Building a Personal Finance Analysis System with LLMs: A Read-Only Security Design and Release Checklist
This article walks through the technical design of a personal finance analysis system powered by large language models. It covers a four-layer architecture in which the model never touches the database directly, explicit JSON Schemas and examples for read-only tool calls, and an authorization lifecycle built on explicit consent, minimal data collection, revocation, deletion and third-party compliance. It also covers rule-first transaction categorization with confidence thresholds and a review queue, anomaly detection rules with false-positive handling, a versioned financial glossary, whitelist validation for chart configurations, a pre-launch security checklist and post-launch evaluation metrics. The core argument: with read-only boundaries, least privilege, revocable authorization and verifiable results carried through the whole pipeline, a financial analysis assistant can be both useful and trustworthy.
1. Why Personal Finance Analysis Needs an LLM
As large language models (LLMs) have matured, questions like “Where did my money go last month?” and “Did any of my subscriptions quietly go up in price?” have moved from wishful thinking to something you can actually build. Three questions tend to come up first:
- How do you build a personal finance analysis system?
- How do you design read-only access so it stays safe?
- Where are the easiest traps to fall into?
This article sets aside any specific product and breaks the problem down from a technical perspective, one point at a time.
The pain points of the traditional approach are concrete:
- Scattered data. Accounts and statements are spread across multiple banks and payment platforms, so there is no unified view.
- A high knowledge barrier. Terms such as annual percentage rate (APR), how interest is calculated and fee rates are not easy for most people to understand.
- Problems that are hard to catch. Recurring charges, subscriptions that quietly increase in price and forgotten management fees rarely surface on their own.
The value of an LLM here is that it connects the whole pipeline, from fetching and analyzing data to explaining and presenting it, through natural language.
2. Overall Architecture and Data Pipeline
The system can be broken down into four layers:
- Data ingestion layer. Balances and transaction records are pulled in through standardized read-only interfaces, such as those offered by open banking.
- Data storage layer. Raw transaction records are archived in object storage, and structured records go into a relational database. Hot and cold data are separated to keep costs under control.
- Capability orchestration layer. An LLM combined with tool calling maps natural language intent to concrete query actions.
- Service and presentation layer. A container platform hosts the backend, logging and monitoring provide observability, and a frontend handles rendering.
The key design principle is that the model never touches the database directly. It can only reach already restricted data through constrained tool functions. The table below lists the permissions and audit points for each layer.
| Component | Read access | Write access | Audit point |
|---|---|---|---|
| LLM | Tool return values only | None | Logs of prompts and tool-call chains |
| Tool layer | Allowlist per account_id |
None (all write operations blocked) | Parameters logged, blocked attempts counted |
| Relational database | Read-only account, masked views | ETL service only | SQL audit log |
| Object storage | Service account reads raw transaction records | ETL writes | Access logs, retention policy |
| Frontend | Structured chart configuration | None | Frontend errors and export actions |
3. Core Capabilities
3.1 Mapping Natural Language to Data Queries
Each tool definition needs a complete JSON Schema that specifies:
- the date format (ISO 8601, UTC+8),
- the amount unit (cents, as integers), and
- the return fields.
Without these, the model will start interpreting units on its own.
{"tools": [{"type": "function","function": {"name": "query_transactions","description": "Query transaction records by date range and account. Read-only; no write operations allowed.","parameters": {"type": "object","properties": {"account_id": {"type": "string", "description": "Account ID; omit to query all authorized accounts"},"start_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD, inclusive"},"end_date": {"type": "string", "format": "date"},"min_amount": {"type": "integer", "description": "Minimum amount in cents"},"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}},"required": ["start_date", "end_date"]}}},{"type": "function","function": {"name": "get_account_balance","description": "Get the balance of a specific account. Read-only.","parameters": {"type": "object","properties": {"account_id": {"type": "string"}},"required": ["account_id"]}}}]}
A user asks, “Where did my money go last month?” The model responds with:
{"name":"query_transactions","arguments":"{\"start_date\":\"2026-07-01\",\"end_date\":\"2026-07-31\",\"limit\":200}"}
The tool returns:
{"currency":"CNY","amount_unit":"cent","items":[{"tx_id":"t_9f2","merchant":"A Coffee Shop","amount":-2800,"category":"Dining","time":"2026-07-03T09:12:00+08:00"}],"truncated":false}
Separating data fetching from interpretation significantly reduces the risk of the model making up numbers.
3.2 Read-Only Permissions, Security Boundaries and Authorization Lifecycle
The principle is simple: the system has read-only access. It cannot transfer funds, and it does not make investment decisions on the user’s behalf. In engineering terms, this means:
- Access restrictions at the interface level. Write endpoints are never exposed.
- Credential isolation. Tokens are kept separate from user identities, keys are stored encrypted, and credentials are rotated every 90 days.
- Explainability and traceability. Every conclusion can be traced back to specific transactions, and important amounts are cross-checked against bank records.
The authorization lifecycle has five steps:
- Explicit consent. Spell out exactly which fields will be read (balances, transaction records, merchant names) and what they will be used for. Pre-checked boxes don’t count as consent.
- Minimal data collection. Data is never pulled in wholesale. If interest can be calculated without it, there is no reason to keep ID document numbers.
- Revocable at any time. After revocation, tokens expire and syncing stops within 24 hours.
- Right to deletion. After a user closes their account, raw transaction records are physically deleted within 30 days. Aggregated results are kept for at most 180 days, and this is recorded in a retention policy table.
- Third-party interface compliance. Document whether data sources are covered by licensing requirements and whether any cross-border data transfer takes place.
One warning: if a network-exposed interface has no authentication or access control, sensitive data is effectively left open to anyone. That is unacceptable from a compliance standpoint.
3.3 Transaction Categorization and Statistics
The approach is rules first, with the model as a fallback. All dependencies need to be defined explicitly:
SUBSCRIPTION_KEYWORDS = ["membership", "subscription", "cloud storage", "streaming", "music"]CONF_THRESHOLD = 0.7 # Below this value, don't auto-categorize; send to the manual review queuedef rule_based_category(t):if any(k in t.merchant for k in SUBSCRIPTION_KEYWORDS):return "Subscription", 1.0if t.type == "transfer" and t.amount < 0:return "Transfer", 1.0return None, 0.0def llm_infer_category(t):# Input contains only merchant, description and amount# Output is {"category": str, "confidence": float}return llm_call(t)def categorize(transactions):review = []for t in transactions:cat, conf = rule_based_category(t)if cat is None:cat, conf = llm_infer_category(t)if conf < CONF_THRESHOLD:review.append(t)t.category, t.confidence = "Needs review", confelse:t.category, t.confidence = cat, confreturn aggregate_by_category(transactions), reviewdef aggregate_by_category(transactions):# Returns [{category, amount_cents, pct, low_conf_cnt}], sorted by amount descending...
Confidence scores must be visible in the UI:
- Low-confidence items on the dashboard get a gray badge labeled “AI inferred” and a one-click link to the review screen.
- Once the same merchant has been manually corrected three times, it is automatically added to the rule set.
3.4 Anomalous Charge Detection
| Detection item | Criteria | False-positive handling |
|---|---|---|
| Duplicate charges | Same merchant, amount differs by ≤ 1 cent, occurs ≥ 2 times at intervals of 1–72 hours | Exclude cases where the user intentionally tapped pay multiple times |
| Unexplained fees | Single charge ≤ CNY 50 with a description containing “handling fee,” “service fee,” “management fee” or “annual fee” | Exclude self-initiated actions such as investment redemptions |
| Silent price increases | The same subscription increases by ≥ 5% for two consecutive billing periods with no notification | Exclude exchange rate fluctuations and expiring discounts |
| Dormant subscriptions | Still being charged after 90 consecutive days with no usage recorded | Ask the user to confirm which channel they use |
The rules engine reliably surfaces suspicious items, while the model explains why they are suspicious. Each alert links to the original transaction record and offers two actions: “Confirmed, no issue” or “Request investigation.” How users respond feeds back into tuning the rules.
3.5 Explaining Financial Terminology
Take a question like “What is APR, and how much interest did it cost me this year?” The system should explain the term first, then work out the numbers. The glossary should record the version and source of each entry, such as public guidance from regulators or terms from bank contracts. When an entry is added, record its source, version and reviewed_at.
The interface should always show a fixed notice: “This is a general explanation only and does not constitute investment advice.” The system then calculates the total interest from actual transaction records, so the answer can be verified.
3.6 Visualization and Custom Dashboards
The model only outputs structured descriptions. It never directly generates executable frontend code.
{"chart":"line","x":{"field":"month","format":"YYYY-MM"},"y":{"field":"amount","unit":"CNY"},"series":["Dining","Transportation"],"filter":{"date_range":"last_6_months"}}
Frontend validation rules:
- The
charttype is checked against an allowlist. - Every
fieldmust match a declared metric. script,htmlandstylefields are not allowed.- Any configuration that fails validation is discarded, and the view falls back to a table.
4. Pre-Launch Checklist
- Read-only verification: attempt write operations with a test account. Every request must return 403.
- Credential rotation: keys rotate automatically every 90 days, and old keys are invalidated immediately.
- Log redaction: card numbers, tokens and ID numbers are masked, and raw transaction records never enter the logs.
- Query auditing: every tool call logs who called it, the parameters, how long it took and how many rows it matched.
- Anomaly interception: requests with an unauthorized
account_idare counted and trigger alerts. - Traceable results: every conclusion can be expanded with one click to show the underlying transactions.
- Disclaimer: the first screen states that “results are for reference only,” and important amounts prompt the user to verify them with their bank.
5. Evaluation Metrics
Once the system is live, track these metrics continuously:
- Query success rate: ≥ 99%
- Tool parameter validity rate: share of tool calls with valid parameters
- Classification accuracy: ≥ 90% on a human-labeled dataset
- Anomaly recall: ≥ 85%
- False-positive rate: ≤ 10%
- Hallucination rate: share of numbers with no supporting data, target < 1%
- P95 response latency: < 3s
- Cost per question: categorized by path, namely a direct cache hit, a single tool call or multiple turns
If any metric misses its target, roll back to the rules-only version.
6. Conclusion
The hard part is not the model itself. It is secure data ingestion, constrained tool design, reliable cooperation between rules and the model, and output that is explainable and traceable. The system is both useful and trustworthy only when read-only boundaries, least privilege, revocable authorization and verifiable results are carried through every stage of the design.
