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.
Metadata
Release files for matlas 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| matlas-0.1.0.tar.gz | 41.7 kB | Without provenance | Details for matlas-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| matlas-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for matlas-0.1.0-py3-none-any.whl |
Total release size: 74.8 kB
Release files / matlas-0.1.0.tar.gz
| Download URL | matlas-0.1.0.tar.gz |
|---|---|
| Size | 41.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4bb708bda00572630613d695305a0cb0335df84ca531bc96d96f860df18adcfa
|
|
BLAKE2b-256 checksum How to use checksums |
599bc5cb9efb672942912770bcec7fca2f11660627f16bfec161122c46dc5750
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / matlas-0.1.0-py3-none-any.whl
| Download URL | matlas-0.1.0-py3-none-any.whl |
|---|---|
| Size | 33.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c2287c9257f185b40ddd9caad77958dbe559156d4d2bb5376e0d051e8cca09b
|
|
BLAKE2b-256 checksum How to use checksums |
d05704b03f0da3bbd4b1fb7674cb77b557d0360dfbe987d3b00d451230461aef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Security for matlas 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.