Turn messy bank/card transaction descriptors into clean structured data — US and India, agentically.
Project description
matlas
Turn messy bank-statement strings into clean, structured data.
If you've ever pulled transactions from a bank feed, you know the problem:
what a human calls "coffee at Starbucks" arrives as
SQ *STARBUCKS #4521 SEATTLE WA, and a food delivery order in India shows
up as UPI/DR/408123456789/SWIGGY/YESB/Payment for order. Closed APIs like
Plaid Enrich, Ntropy, and MX solve this behind a paywall. matlas is the
open-source alternative: give it the raw descriptor, get back the merchant,
a spending category, a confidence score, and the evidence trail (including
the MCC, where one exists) that backs them up.
$ matlas enrich "SQ *STARBUCKS #4521 SEATTLE WA"
{
"raw": "SQ *STARBUCKS #4521 SEATTLE WA",
"region": "US",
"rail": "card",
"merchant": "Starbucks",
"category": "food_and_drink",
"confidence": 0.95,
"consistency_check_applicable": true,
"consistency_ok": true,
"evidence": [
{"source": "llm", "detail": "Starbucks", "confidence": 0.9},
{"source": "resolver", "detail": "gazetteer_exact:5814", "confidence": 1.0}
],
"is_unknown": false
}
Why it's built as an agent (and why that matters)
Most LLM wrappers ask the model to guess a category and call it a day. The problem: models are confidently wrong about merchants, and there's no way to tell a good guess from a bad one.
matlas splits the job in two:
- The LLM proposes. It reads the raw descriptor and suggests a
merchant and category — the thing LLMs are genuinely good at
(untangling
SQ *, store numbers, city suffixes). - A deterministic resolver checks. A curated merchant gazetteer (exact match, then fuzzy match) looks up the authoritative category and MCC. This is a real tool call inside the agent loop — a lookup table, never a second LLM guess.
When the two disagree, a validator (core/validator.py) catches the
contradiction and sends it back: the model sees the resolver's evidence and
gets exactly one bounded retry to reconsider. If the resolver has high
confidence, its answer wins. Every returned record carries the evidence
trail, so you can see why matlas believes what it believes.
The result: hallucinations get caught by a lookup table instead of shipping to your ledger.
Two regions, one honest difference
The core is region-agnostic; regions plug in as swappable RegionPacks.
- US — the benchmarked pack. Card descriptors resolve against a
330-row gazetteer with real,
iso18245-verified MCC codes. This is where accuracy claims live, because MCC gives us ground truth to measure against. - India — the portability pack. UPI narrations (
UPI/DR/.../SWIGGY/...) are parsed, merchant payees resolve against a 186-row gazetteer, and person-to-person transfers get carved out aspersonal_transfer(there's no meaningful "category" for paying your friend back for dinner). India ships without an accuracy claim — no public labeled corpus of Indian bank narrations exists, so any number we printed would be made up. The pack demonstrates that the architecture ports; it doesn't pretend to a benchmark that can't exist yet.
That asymmetry is deliberate. We'd rather tell you what we can't measure than invent a number.
Install
pip install -e ".[dev]" # library + CLI + tests
pip install -e ".[api,dev]" # + REST API / MCP server
You need an ANTHROPIC_API_KEY in the environment for anything that runs
the agent loop (CLI, API, MCP). The offline test suite
(pytest tests --ignore=tests/live) needs no key — the agent loop is
tested against replayed response cassettes, which is also what keeps CI
green without secrets.
Using it
CLI:
matlas enrich "SQ *STARBUCKS #4521 SEATTLE WA"
matlas enrich "UPI/DR/408123456789/SWIGGY/YESB/Payment for order" --region india
--region accepts auto (default — detected from the descriptor's shape),
us, or india.
Bulk CSV — the realistic path. Nobody enriches one transaction at a time; you have a statement export or a feed dump. Point matlas at a CSV with a descriptor column and it runs every row through the cost-tiered batch path (exact gazetteer hits never touch the LLM — a file of known merchants costs nothing and needs no API key):
matlas enrich-csv transactions.csv --column descriptor --out enriched.csv
Output is the input CSV with merchant, category, confidence, and
is_unknown columns appended. Bank gave you a PDF? Export the CSV from
your net-banking portal instead — every major bank offers it, and PDF
table extraction is a different problem than transaction enrichment.
REST API (matlas serve --api, FastAPI on :8000):
POST /enrich {"descriptor": "...", "region": "us"} -> EnrichedTransaction
POST /enrich/batch {"descriptors": [...], "region": "us"} -> [EnrichedTransaction]
GET /healthz
The batch endpoint is cost-tiered: an exact gazetteer hit skips the LLM entirely (free), a fuzzy hit gets confirmed by a cheap model, and only genuinely ambiguous descriptors escalate to the strong model. On realistic batches, most rows never touch the expensive path.
MCP server (matlas serve --mcp, stdio transport): exposes one tool,
enrich(descriptor, region=None), returning the same structured record —
plug it into Claude Desktop or any MCP client and let the model enrich
transactions mid-conversation.
Measuring it
The eval harness runs the gold set (the same curated gazetteer rows, 330 US + 186 India) through the full agent loop and reports per-field accuracy plus mean tool calls per transaction:
from matlas.eval.gold import load_gold
from matlas.eval.harness import run_benchmark
from matlas.eval.report import render_report
render_report(run_benchmark(agent, load_gold()))
Judging defaults to exact match; an LLM-as-judge (cheap model, one-turn same-merchant check) is available for fuzzy merchant-name comparison.
The report includes a confidence-calibration table — predictions bucketed by reported confidence, each bucket's mean confidence side by side with its actual accuracy. When matlas says 0.9, that table is how you check it was right about nine times in ten. Calibration here is measured, never tuned blind. India results are labeled a portability smoke test in the report output, never an accuracy benchmark — see above.
Privacy stance
Descriptors you enrich are sent to the Anthropic API and nowhere else. matlas keeps no telemetry, no logging of your data, and no server-side anything — it's a library. The gazetteer lookup happens locally. If a descriptor resolves with an exact gazetteer hit through the tiered batch path, it never leaves your machine at all.
Contributing
The highest-leverage contribution is gold-set rows: real (anonymized)
descriptor shapes, especially Indian bank narrations, where no public
corpus exists. Second-highest: a new region pack — the whole point of the
RegionPack seam is that your country's rails plug in without touching
the core. CONTRIBUTING.md walks through both, including
the data-honesty rules (every US MCC verified against iso18245, no
invented benchmarks).
Status
Weeks 1–3 of the build plan complete and verified end-to-end against the live API: agent loop, US + India packs, CLI, REST API, MCP server, cost-tiered batching, LLM-judge, eval harness, CI. Week 4 (launch polish, PyPI release) in progress. Design docs and the full build log live outside this repo.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file matlas-0.1.0.tar.gz.
File metadata
- Download URL: matlas-0.1.0.tar.gz
- Upload date:
- Size: 41.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bb708bda00572630613d695305a0cb0335df84ca531bc96d96f860df18adcfa
|
|
| MD5 |
ac266d84ad1b7a0ec3156d08a847fc19
|
|
| BLAKE2b-256 |
599bc5cb9efb672942912770bcec7fca2f11660627f16bfec161122c46dc5750
|
File details
Details for the file matlas-0.1.0-py3-none-any.whl.
File metadata
- Download URL: matlas-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c2287c9257f185b40ddd9caad77958dbe559156d4d2bb5376e0d051e8cca09b
|
|
| MD5 |
0c3dcf8bcf52705a7a7ed1e538352348
|
|
| BLAKE2b-256 |
d05704b03f0da3bbd4b1fb7674cb77b557d0360dfbe987d3b00d451230461aef
|