# Building a Personal Finance Analytics System: Architecture, Security, and LLM Integration

    A practical guide to building a personal finance analytics system with natural language interaction: layered architecture, read-only bank account linking, transaction normalization, spending analysis, and privacy safeguards.

    1. Background: Why Personal Finance Analysis Needs Data Plus Language

    Personal money management has long suffered from two pain points. First, accounts are scattered across multiple banks and credit card providers, forcing users to hop between apps to piece together a complete picture of income and spending. Second, raw statements are just transaction logs—no categorization, comparisons, or trends—so users end up exporting spreadsheets and checking entries line by line.

    Natural language interaction offers a new entry point. Users can ask direct questions such as “How much higher was my dining spend last month compared to the average of the previous two months?” The system must understand the question, fetch the right data, perform the calculation, and present the conclusion in plain language. This pipeline involves four stages: data ingestion, data processing, computation and analysis, and result presentation—each with its own technical challenges.

    Because these features handle highly sensitive financial data, and accuracy directly affects user decisions, the engineering focus should not be on “can it answer questions,” but on “is every answer traceable, is the data under control, and are the boundaries clearly defined.”

    2. Overall Architecture

    A good approach is to divide the system into five layers, each with a single clear responsibility, making independent testing and replacement easy.

    1. ┌────────────────────────────────────────────────────────┐
    2. │ Interaction layer: natural language input, charts, │
    3. │ and dashboard rendering │
    4. ├────────────────────────────────────────────────────────┤
    5. │ Understanding layer: intent parsing, query planning │
    6. ├────────────────────────────────────────────────────────┤
    7. │ Computation layer: category stats, recurring-payment │
    8. │ detection, anomaly detection, │
    9. │ interest calculation │
    10. ├────────────────────────────────────────────────────────┤
    11. │ Data layer: normalized transaction tables, account │
    12. │ snapshots, audit logs │
    13. ├────────────────────────────────────────────────────────┤
    14. │ Ingestion layer: read-only authorization, account │
    15. │ syncing, token management │
    16. └────────────────────────────────────────────────────────┘

    The core design principle: numbers are computed by deterministic code, and the language model is only responsible for understanding the question and explaining the result. LLMs performing arithmetic directly are error-prone and cannot show their work. Pushing computation down into testable code means every conclusion can carry transaction IDs so users can verify it.

    3. Read-Only Data Ingestion and Authorization

    3.1 Least-Privilege Principles

    Account linking is the starting point of the pipeline and where security risk is most concentrated. Recommended constraints:

    • Request read-only scopes only—no transfer, payment, or transaction permissions.
    • The system never touches the user’s banking login credentials; authentication happens on the institution’s own pages (e.g., through an open banking aggregator).
    • Fetch only the fields needed for analysis: institution name, account type, last four digits of the account number, balance, credit limit, and transactions.
    • Keep authorization validity under user control. Users can revoke access at any time, and revocation should immediately stop syncing and delete the corresponding tokens.

    3.2 Authorization Flow

    A typical flow has four steps: the application initiates an authorization request, the user completes verification on the institution’s page, the platform returns an access token, and the server stores the token encrypted. The following pseudocode is illustrative—the interface names are fictional examples, not any real service’s API:

    1. function startAccountLink(userId):
    2. session = aggregator.createLinkSession(
    3. scopes = ["accounts:read", "transactions:read"],
    4. callbackUrl = APP_CALLBACK_URL,
    5. state = generateCsrfToken(userId)
    6. )
    7. return session.redirectUrl
    8. function onLinkCallback(code, state):
    9. verifyCsrfToken(state)
    10. token = aggregator.exchangeCode(code)
    11. vault.store(userId, encrypt(token))

    Two key points here. First, the state parameter guards against cross-site request forgery and must be validated on callback. Second, access tokens should only ever be stored in encrypted form, with keys held in a separate key management service—never hard-coded or written to logs.

    3.3 Historical Data Scope

    The time range for pulling transactions needs an explicit upper bound. The common practice is to sync a limited historical window on first sync, then switch to incremental sync. A longer window means stronger analysis but higher storage and privacy risk, so it should be sized to actual business needs and disclosed to users in the product description.

    4. Transaction Normalization

    Raw transaction descriptions are usually messy. The same merchant may appear under multiple spellings, mixed with reference numbers, dates, and region codes. Without preprocessing, downstream categorization and recurring-payment detection will be distorted.

    4.1 Normalization Steps

    Normalization typically includes:

    1. Unify amount signs and currencies—record spending as negative values and convert at a consistent exchange rate.
    2. Strip noise from description fields: reference numbers, dates, store IDs.
    3. Normalize merchant names, mapping different spellings to a single merchant entity.
    4. Assign a category label to each transaction, using a combination of rules and models.
    5. Tag special types—transfers, payments, refunds—to avoid double counting later.
    1. def normalize_merchant(raw_desc):
    2. text = raw_desc.upper()
    3. text = remove_noise_tokens(text) # strip reference numbers, dates, region codes
    4. text = strip_trailing_digits(text)
    5. return MERCHANT_ALIAS.get(text, text) # alias table maps to canonical name

    4.2 Handling Special Transactions

    Credit card payments and account-to-account transfers are the two transaction types most likely to skew statistics. If you count a credit card payment as spending while also counting credit card purchases as spending, the same money gets counted twice. The correct approach: record a payment as a liability reduction, excluded from total spending; account-to-account transfers only affect balances, not income or expenses. Refunds should offset the original spending category rather than count as income.

    5. Core Analytical Capabilities

    5.1 Category Statistics and Monthly Balance

    Category statistics are built on the normalized transaction table. Aggregating by category and month yields each spending category’s share and trend.

    Monthly balance is calculated as:

    Monthly balance = total income for the month − total spending for the month (excluding transfers, payments, and refund offsets)

    “Income” here should mean real inflows such as salary or business earnings—not any credited amount. The result should be reconciled against the change in account balances; if the discrepancy exceeds a threshold, flag it to the user rather than outputting a conclusion directly.

    5.2 Recurring Payment Detection

    Membership renewals, subscriptions, and insurance premiums are usually charged at fixed intervals. Detecting them helps users spot spending that is easy to overlook.

    The basic idea: group by merchant and amount, then check whether the intervals between consecutive charges are stable.

    1. for group in group_by(merchant_key, amount_bucket):
    2. dates = sorted(group.dates)
    3. if len(dates) < 3:
    4. continue
    5. intervals = diff(dates)
    6. if stdev(intervals) <= INTERVAL_TOLERANCE and mean(intervals) in CYCLE_CANDIDATES:
    7. mark_recurring(group, cycle=classify_cycle(mean(intervals)))

    CYCLE_CANDIDATES might include a roughly 30-day monthly cycle and a roughly 365-day annual cycle. INTERVAL_TOLERANCE and the amount bucket width are tunable parameters that should be determined by backtesting against real samples, not treated as fixed standards.

    5.3 Anomaly and Sustained Increase Detection

    Anomalous charges can be found by comparison against historical baselines. Common methods:

    • Single-charge anomaly: the current amount exceeds the historical distribution for the same merchant by several standard deviations.
    • Cost increase: a recurring charge increases over multiple consecutive periods, with cumulative growth beyond a set threshold.
    • Duplicate charge: the same merchant and amount appears twice in a short window, outside normal installment or split-order scenarios.

    These rules are heuristic, and false positives are common. Detection results should therefore be labeled as “suspected,” paired with the historical records used for comparison, and confirmed by the user.

    5.4 Interest and Debt Cost Estimation

    Users sometimes ask for the total interest paid over a year. Interest estimation typically converts an annual rate to a daily rate:

    Daily rate = annual rate ÷ 365

    Monthly interest ≈ average daily balance × daily rate × days in the month

    Annual interest = sum of monthly interest

    Different institutions use different day-count conventions, compounding rules, and fee structures. Estimates should therefore be treated as references only—the UI should note “refer to the institution’s statement”—and the actual interest fields from statements should be read where possible.

    6. Natural Language Queries and Result Presentation

    6.1 From Question to Query Plan

    User questions must first be parsed into a structured query plan, then executed by the computation layer.

    1. query = "How much higher was my dining spend last month than the previous two months' average?"
    2. plan = parse_intent(query)
    3. # {
    4. # metric: "spend_sum",
    5. # category: "dining",
    6. # period: "last_month",
    7. # baseline: "prev_2_months_avg"
    8. # }
    9. result = engine.execute(plan) # deterministic computation, returns values and transaction IDs
    10. answer = explain(result, cite=result.txn_ids) # the model generates the explanation, citing evidence

    The parsing stage should be constrained by a whitelist—only predefined metrics, categories, and time ranges may be queried. Out-of-scope questions should be explicitly declined, not left for the model to improvise.

    6.2 Charts and Dashboards

    Common presentation formats include weekly spending bar charts, billing calendars, and personal finance dashboards. These are all generated from structured data produced by the computation layer; the frontend only renders. The benefit: the same data stays consistent across views, and each component can be tested independently.

    7. Accuracy and Reliability

    Analysis results can be wrong—the system design should accept this and provide ways to verify.

    1. Traceability: every conclusion is linked to transaction IDs the user can inspect one by one.
    2. Reconciliation checks: aggregated results are compared against account balance changes; large discrepancies downgrade output to a warning.
    3. Versioning: categorization rules, alias tables, and detection thresholds carry version numbers, and results record the version used for reproducibility.
    4. Explicit disclosure: the UI states the data source, sync time, and the applicable scope of conclusions.

    For uncertain categorizations, mark them “needs review” rather than forcing an assignment. A wrong category corrupts every downstream statistic; the cost of a review flag is far smaller.

    8. Privacy, Security, and Compliance

    8.1 Data Protection Measures

    • Encrypt data in transit with standard protocols, and store data at rest with widely accepted strong encryption.
    • Retain only the last four digits of account numbers; never persist the full number.
    • Mask amounts, merchants, and account identifiers in logs so sensitive data never reaches logging systems.
    • Apply least-privilege access control; operations staff cannot view user-level detail by default.

    8.2 Data Usage Boundaries

    If the product promises that certain conversations will not be used for model training, that promise must be enforced technically, not just written into the terms. Common practices include isolating user financial data in a separate data domain, defining explicit exclusion rules in training data pipelines, and auditing data flows. The promise should match the actual data flow and be reviewed periodically.

    8.3 User Control

    Users should be able to view connected accounts, revoke authorization at any time, and request deletion of related data. After revocation, the system should stop syncing and handle historical data according to a retention policy. The deletion process needs a clear completion state and an audit record.

    8.4 Compliance Notes

    Regulatory requirements for financial data vary by region—consult legal and compliance staff before development. Analytics features provide information organization and statistical results; they do not constitute investment or credit advice. For complex financial planning, the system should direct users to licensed professionals.

    9. The Boundary of Human–Machine Collaboration

    Tasks well suited to the system: data organization, category statistics, pattern recognition, and anomaly alerts. Tasks requiring human judgment: whether income is stable, whether debt is sustainable, whether to adjust investment or insurance allocations.

    Product design should keep these two categories clearly separate. The system can say “credit card interest this month increased versus last month,” but should not directly give decision advice like “you should pay early”—unless it has the relevant compliance credentials and a complete risk assessment process.

    10. Engineering Launch Checklist

    Before shipping a personal finance analytics feature, confirm the following item by item:

    • Authorization scope is read-only and revocable at any time.
    • Tokens are stored encrypted, with keys managed separately from data.
    • Transaction normalization rules have a test suite covering transfers, payments, refunds, and other special cases.
    • Recurring-payment and anomaly detection thresholds are backtested against real samples.
    • Every analytical conclusion is traceable to specific transactions.
    • Estimates such as interest state their calculation basis and applicable scope.
    • Logs are sanitized; sensitive fields never reach monitoring or analytics pipelines.
    • Privacy promises match the actual data flow, with periodic reviews.
    • Users can view, export, and delete their own data.

    The value of personal finance analytics comes from accurate, verifiable data processing—not from the conversation itself. Layering data ingestion, computation, and language interpretation, with clear permissions and validation at each layer, is the foundation for making these features reliable.