跳到正文
DEV Community· Tunde Jonah Olabanjo·· 3 小時前AI 評分47

Ghost in the Ledger:以開放權重 AI 找出銀行月結單中的異常支出

Ghost in the Ledger: open-weight AI that finds the ghosts haunting your bank statement

AI 導讀

Ghost in the Ledger 以確定性程式分析銀行月結單或交易提示,找出訂閲、手續費、深夜支出及薪金快速用盡等模式,再由模型改寫成故事;模型不負責判定交易或數字。它支援 Ollama 本機運行、Groq 雲端模型及無模型離線模式,並會核對模型生成內容中的數字,未通過檢查便改用範本。

正文

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

What I Built

I live in Nigeria, where every debit comes with an SMS or an email: a POS payment at 2 AM, a small SMS fee, a subscription you forgot about, a salary that landed on the 28th and was mostly gone by the 1st. Then I noticed everyone's phone does this: a card alert, a "you made a $12.99 purchase", a pound here, a euro there. At the end of the month, nobody knows where the money went.

Ghost in the Ledger takes a bank statement (PDF or CSV) or pasted alerts and SMS from almost any bank, in many currencies, and shows you the ghosts hiding in it:

Subscription ghosts: the same place charging about the same amount every month.
Fee poltergeists: overdraft, ATM and foreign transaction fees, SMS charges, VAT and stamp duty, added up.
Midnight spenders: debits between 12 AM and 5 AM.
Payday vanishing acts: a big credit where most of it is gone within three days.
Leaks: many small debits to the same place.

Each ghost gets a short, spooky story and the exact lines behind it, so every claim can be checked.

My reading of the theme: the past is still in your account, and it keeps leaving marks. A ghost that says "your salary vanished in three days" is harder to ignore than a spreadsheet row.

The part I care about most is the one you cannot see: the model is never allowed to be wrong about your money. More on that below.

Demo

All demo data is fake. The app has three demo sets (US dollars, UK pounds, Nigerian naira) so anyone can try it without a statement.

A real result from the US demo, written by gpt-oss-20b through Groq:

The Payday Vanishing Act. On 2026-08-31 a friendly ghost paid a credit of 2850 USD, then vanished after spending 2424.99 USD, leaving a spectral 85% balance.

(That is the model's own wording, quirks included. The numbers are checked against the data, the prose is the model's.)

Code

GitHub logo OlatundeOlabanj / ghost-in-the-ledger

Local-first AI that finds the ghosts in your Nigerian bank alerts: forgotten subscriptions, fee drains, midnight spends. A guard stops the model from inventing numbers. Zero dependencies.

Ghost in the Ledger

Paste your bank alert emails, SMS texts or a statement, from almost any bank in the world, and a friendly ghost shows you what is quietly haunting your money: forgotten subscriptions, small bank fees, late-night spends, a payday that vanished in three days.

Built for the DEV Hacktoberfest "Ghost in the Machine" challenge. It uses open-weight models (Gemma, Llama, gpt-oss) and has zero npm dependencies.

Privacy story

Your money data is yours.

  • The server only listens on 127.0.0.1, so nothing outside your computer can open it.
  • By default everything runs on your machine. If Ollama is installed, a small open-weight model writes the ghost stories locally.
  • No Ollama? There is a built-in offline mode that writes the stories from templates. It needs no model and no internet.
  • Groq (hosted open-weight model, default openai/gpt-oss-20b) is an optional fallback. It is only used if you set…

MIT licensed. Node 18 or newer. No npm install, because there are zero dependencies. npm start and open http://127.0.0.1:8787.

How I Built It

Finding ghosts is plain code. The model only tells the story.

lib/ghosts.js finds every ghost with deterministic code: same input, same ghosts, same numbers. The model never decides what happened. It receives the facts of one finding plus a few example lines, and its only job is to rewrite them as a short story.

Three backends, picked automatically:

Ollama, fully local. I ran Gemma 3 1B (4-bit Q4_K_M) on a 2-core, 3.6 GB laptop with no GPU. Works with the internet off.
Groq, hosting an open-weight model (openai/gpt-oss-20b by default), only if you set GROQ_API_KEY. Optional. Even then, the whole statement is never sent, only short facts about each pattern.
Offline mode, which writes the stories from templates. No model, no internet, always works.

The story guard. Small models invent numbers, names and sometimes copy raw data. For a money app that is not acceptable, so every model story has to pass checks before you see it:

Every number must exist in the facts or the example lines. Spelled-out numbers, k and million shortcuts, and vague amounts ("double", "a dozen") are refused.
A date part, like the 12 in "12 Aug", cannot pose as a count of 12 payments.
The text must read like a story: no copied data rows, no | separators, no starting with a date.

If any check fails, the story is thrown away and a template written from the real facts takes its place. The page tells you how many were replaced and shows what the model wrote. The worst case is a plainer story, never a wrong number.

Proof, not promises. A bar at the top counts every outbound request the server makes and the bytes it sent. With Ollama or offline mode it stays at zero. With Groq it shows the true number. A panel after each run lists what a cloud app would have uploaded, struck through, and what was actually sent instead. The meter only counts requests this app makes. It cannot watch other programs, and the README says so.

Any bank, many currencies. Currency is detected per transaction, US versus UK date order is decided per file, and mixed currencies are never added together. PDF statements are converted on your own computer with pdftotext and never stored. The parser was written against fake alerts and statements shaped like US and UK banks and wallets, and like GTBank, Access, Zenith, UBA, Opay, Kuda, Moniepoint, Sterling, FirstBank, Ecobank and ALAT. These are my own fake samples in those shapes, not real alerts, so a bank that changes its wording shows up as a skipped line with a warning that quotes it. It does not guess.

What I measured, honestly:

Gemma 3 1B, local, on my laptop. The US demo with a one-story limit took about 3 to 4 minutes for that single story, at roughly 2 tokens per second on 2 cores and almost no free memory. It finished, with zero outside calls. The guard replaced the story. Gemma had written Okay, here are two spooky-friendly sentences about the spending, keeping the numbers exactly as they were: "The money's flowing, a little bit slow, 2850 USD...", which does not read like a story, so the page used the template instead. My first Gemma attempt had also copied a raw data row (2026-08-31 credit 2850 USD | A tiny spectral bill…) with real numbers, which my first guard accepted. That mistake is why the readability checks exist.
gpt-oss-20b on Groq. All five stories in seconds. One of five was replaced by the guard, for starting with a copied date and data row. The meter showed 5 outside calls and 5,287 bytes sent, and the page said so.
Tests: 191 tests, zero dependencies, no CDNs, no external fonts. The server only listens on 127.0.0.1 and refuses foreign Host headers.

Known gap, stated plainly: the guard proves every number is real, not that it sits in the right place. If a model swaps two true numbers, it can slip through. A 1B model on a 2-core laptop is also slow. A recent laptop will do far better, and the app has settings for slow machines (GHOST_COMPACT, GHOST_MAX_STORIES, GHOST_TIMEOUT_MS).

I built this with AI coding assistance, which the challenge allows. The design decisions, the privacy rules and the guard checks are mine, and I ran and tested the app myself, on my own laptop.

Why Does Open Innovation Matter?

A bank statement is about the most private file most people own. For this app the open-weight model is not a nice extra. It is the reason the app can exist at all:

Your data never has to leave your computer. With Ollama running, nothing is uploaded, and the meter proves it for this app. A closed API would mean sending someone's transactions to a server they do not control.
It works with the lights and the internet off. Where data is expensive and connections drop, that matters. I downloaded my model over a connection that kept stalling, so I chose a 0.8 GB model on purpose.
I can swap models. When the model I had chosen as the default was no longer on Groq's list, the app returned a plain "not found" error, and I switched to another open-weight model with one environment variable. Nothing in my project was locked to one provider.
It costs nothing to run. No per-token bill for a tool you should use on a quiet Sunday night.

I have not benchmarked closed models against this, so I will not claim better quality. What I can say is that the privacy and cost story only works because the weights are open, and that a 1B local model was too slow and too error-prone on my hardware to write every story, which is exactly why the guard exists.

The Spirit of It

The Nigerian demo statement ends with a salary of ₦450,000 and a ghost that says 92% of it was gone in three days. Nobody needs a model to tell them that. But the money you lose slowly is the part you never see, and a ghost saying it out loud is harder to look away from.

Prize Categories

Best Use of Gemma: Gemma 3 1B runs locally through Ollama as the story writer, with the guard checking its output. I am being plain about the result: on my hardware it wrote one story, slowly, and the guard replaced it.

來源:DEV Community · dev.to