Case Study

Agent 17 · Transaction Narrative Enrichment

From Cryptic Bank Code
to Human-Readable Truth

How a six-field output schema and a stateless single-turn architecture transforms raw acquirer strings into clean, categorised transaction narratives — with a confidence signal built in for downstream triage.

Model claude-sonnet-5-5
System Prompt < 300 tokens
Max Output 800 tokens
API Calls 1 per batch
FinTech Transaction Enrichment Classification Structured Output

The Problem

Bank transaction strings are generated for payment processing infrastructure — not for humans. What lands in a customer's account history is often the acquirer reference, a truncated legal entity name, or a POS terminal code: SQ *PRET A MANGER LONDON GB or AMZN MKTP UK*2K4L9.

For consumer-facing fintech products — PFM apps, open banking platforms, spend analytics dashboards — this creates a costly enrichment problem. Raw strings must be resolved to real merchant names, categorised into spend buckets, and described in language customers recognise. Traditionally this requires merchant lookup databases, category taxonomies, and rule-based classifiers that need continuous maintenance. The long tail of novel, ambiguous, or regional strings breaks every rule-based system eventually.

Enrichment Example

AMZN MKTP UK*2K4L9 WWW.AMAZON.CO.UK
↓ enriched in a single API call
Merchant
Amazon Marketplace UK
Category
Retail
Channel
Online
Confidence
HIGH
Description
Online purchase from Amazon Marketplace UK

Design Approach

Agent 17 is stateless by design — each batch of transaction strings is a single API call returning a complete set of enrichment records. There is no conversation history, no follow-up turns, no state to manage. This mirrors how transaction enrichment would operate in a real pipeline: a batch in, structured records out.

The core schema enforces six fields per transaction: RAW (the original string reproduced for traceability), MERCHANT, DESCRIPTION, CATEGORY, CHANNEL, and CONFIDENCE. Each field maps to a specific downstream use — merchant name for display, category for spend analytics, channel for fraud signals, confidence for routing to manual review queues.

Key design decision: The CONFIDENCE field is a triage signal, not a quality hedge. HIGH means the string contained enough unambiguous signals to enrich with certainty. LOW means the string is genuinely ambiguous and the enrichment is an inference — the downstream system should route it to a human review queue or a fallback lookup. This distinction is load-bearing for real-world pipeline integration.

The input uses a monospace textarea — a deliberate choice. Transaction strings are technical data, not natural language, and the font signals to the user that raw, unformatted strings are expected. The send button is labelled "Enrich" rather than "Send" or "Analyse" to name the operation precisely.


System Prompt Architecture

The prompt defines a single repeating delimiter block — ===TXN=== to ===END=== — with six named fields. The frontend parses each block and renders it as a transaction card with the raw string, merchant name, description, and two metadata tags (category and channel), plus a colour-coded confidence badge.

Merchant Resolution

The prompt explicitly distinguishes merchant name from acquirer code — the model must return the real trading name, not the payment processor reference. This prevents outputs like "SQ *" appearing as the resolved merchant.

Category Taxonomy

A fixed 11-category list is specified in the prompt — Groceries, Dining, Travel, Transport, Retail, Subscriptions, Utilities, Healthcare, Entertainment, Finance, Other. Constraining the taxonomy prevents free-form category invention and makes outputs machine-readable downstream.

Channel Classification

Six channel options — In-Store, Online, Contactless, Direct Debit, ATM, Transfer, Unknown — are defined as a closed list. Channel inference from string patterns (e.g. a URL suffix → Online, DD REF → Direct Debit) is a signal the model picks up reliably.

No-Skip Rule

An explicit prompt constraint — "process every transaction string, never skip one" — prevents the model from silently dropping ambiguous strings. Every input line must produce a TXN block, even if the enrichment is low-confidence.


What I Learned

The monospace input is a UX signal, not just aesthetic. Using a monospace font in the textarea communicates that raw, technical strings are expected — not sentences or paragraphs. It reduces the cognitive friction of pasting data directly from a terminal or CSV export, and aligns the interface with how practitioners actually work with transaction data.

Closed taxonomies are load-bearing. Without a fixed category list, the model produces categories that are semantically correct but machine-unreadable — "Fast Food," "Quick Service Restaurant," and "Takeaway" all mean the same thing but would create three separate spend buckets in a PFM dashboard. A constrained list of 11 categories eliminates this entirely.

Confidence is triage, not hedging. The original design had CONFIDENCE as a quality signal. Testing revealed it works better as a routing signal — HIGH goes straight to display, LOW goes to a review queue. Reframing the field in the prompt produced more calibrated outputs than asking the model to "rate its certainty."

The no-skip rule matters more than it seems. Without it, the model silently drops strings that look like internal reference codes or genuinely ambiguous entries — exactly the strings a fraud analyst most needs to review. Making skipping impossible, and requiring a LOW-confidence enrichment instead, keeps the pipeline complete.

Stateless is correct for batch enrichment. Unlike a conversational agent, transaction enrichment has no memory dependency. Each batch is independent. Stateless architecture reduces latency, eliminates context window bloat, and makes the output deterministic — every run on the same input produces the same structure.


Typical Token Profile

A batch of 6–8 transaction strings enriched in a single call typically produces:

System: ~270 tokens User input: 80–180 tokens Output: 400–700 tokens Total: ~750–1,150 tokens / run