Claude
Skills
Sign in
Back

pdf-statement-parser

Included with Lifetime
$97 forever

Parses a single financial statement PDF (checking, savings, credit card, brokerage, 401k, HSA, mortgage, tax form) and emits a normalized JSON record with institution, account mask, statement period, opening/closing balances, line-item transactions or holdings, and a confidence score. Use when extracting structured data from a bank, brokerage, retirement, or HSA PDF statement, when ingesting a drop of household finance documents, or when user mentions parsing a statement, extracting transactions from a PDF, or normalizing statement data.

Writing & Docs

What this skill does


# PDF Statement Parser

## Table of Contents
- [Overview](#overview)
- [Input contract](#input-contract)
- [Workflow](#workflow)
- [Document type playbooks](#document-type-playbooks)
- [Output contract](#output-contract)
- [Confidence scoring](#confidence-scoring)
- [Guardrails](#guardrails)
- [Example](#example)

## Overview

Bank, brokerage, retirement, HSA, and mortgage statements arrive as PDFs in many house-styles. This skill is the prompt-driven extraction methodology: identify the document type, locate the canonical fields by their labels (not position), pull every transaction or holding row, and emit a strict JSON record. All monetary values become **integer cents**, all dates become **ISO 8601 (YYYY-MM-DD)**.

Use directly with Claude's PDF reading: `Read` the PDF, then run this skill's workflow on the visible content. No external library needed.

## Input contract

The caller provides:

- `pdf_path` — absolute path to a single statement PDF.
- `expected_type` (optional) — one of `checking_statement | savings_statement | credit_card_statement | brokerage_statement | 401k_statement | hsa_statement | mortgage_statement | tax_form | insurance_doc | unknown`. If absent, the skill detects it.
- `known_accounts` (optional) — array of `{id, institution, mask, type}` for matching.

## Workflow

```
- [ ] Step 1: Read the PDF (first pass: pages 1-2 for header)
- [ ] Step 2: Detect document type from header keywords
- [ ] Step 3: Extract account identity (institution + mask + type)
- [ ] Step 4: Extract statement period (start/end dates)
- [ ] Step 5: Extract balance markers (opening / closing / available)
- [ ] Step 6: Extract line items (transactions, holdings, or events)
- [ ] Step 7: Normalize to integer cents and ISO dates
- [ ] Step 8: Score confidence on each field; emit JSON
```

### Step 1 — Read the PDF

Use the `Read` tool with the PDF path. For PDFs > 10 pages, read in chunks: pages 1–5 (header + first transactions), then subsequent ranges as needed. Track total page count.

### Step 2 — Detect document type

Look for these header signals (in order of authority):

| Signal | Document type |
|---|---|
| "Statement of Account" + "Available Balance" + checking-style transactions | `checking_statement` |
| "Savings Statement" / "Money Market Statement" | `savings_statement` |
| "Credit Card Statement" + "Payment Due" + "Minimum Payment" | `credit_card_statement` |
| "Account Summary" + "Holdings" + ticker symbols | `brokerage_statement` |
| "401(k) Statement" / "Retirement Plan" + "Vested Balance" | `401k_statement` |
| "Health Savings Account" / "HSA" + "Contribution Limit" | `hsa_statement` |
| "Mortgage Statement" + "Principal" + "Escrow" | `mortgage_statement` |
| "W-2" / "1099" / "1098" / "5498" form numbers | `tax_form` |
| Policy declaration page, premium, deductible | `insurance_doc` |

If two signals tie, prefer the one in the document title or page header.

### Step 3 — Extract account identity

- **Institution**: top-of-page logo text or "Issued by" / "Statement from" label.
- **Mask**: last 4 digits — search for `****1234`, `xxxx1234`, `Account ending in 1234`, or the redacted form the institution uses. Always store as `****1234`.
- **Account type**: derived from §2 above plus visible labels ("Checking", "Visa Signature", "Roth IRA").
- **Owner names**: full names of account holders (used for matching to `household.json` members downstream).

Match against `known_accounts` by `(institution, mask)`. If no match, mark `account_match: "new"` and propose an `account_id` like `acc_<type>_<incrementing>`.

### Step 4 — Extract statement period

Find labels: "Statement Period", "Cycle Date", "Closing Date", "Period Beginning … Period Ending". Output:
- `period_start` — first day covered (ISO).
- `period_end` — last day covered, also the statement closing date (ISO).

If only a closing date is present, set `period_end = closing_date` and `period_start = null` with `confidence: 0.6` on the period.

### Step 5 — Extract balance markers

For cash/credit accounts: `opening_balance_cents`, `closing_balance_cents`, `available_balance_cents` (if present). For credit cards, also `statement_balance_cents`, `minimum_payment_cents`, `payment_due_date`.

For brokerage / 401k / HSA: `total_value_cents`, `cash_cents`, `invested_cents`. Skip opening/closing transaction balances — these are holdings statements.

For mortgages: `current_principal_cents`, `monthly_pi_cents`, `monthly_escrow_cents`, `next_payment_due`, `extra_principal_ytd_cents`.

### Step 6 — Extract line items

Three modes:

**Cash/credit transactions.** For each row in the transactions table, capture: `date`, `post_date` (if separate column), `description_raw`, `amount`. Sign convention: outflows negative, inflows positive. If statement uses two columns ("Withdrawals" and "Deposits"), set sign accordingly. Preserve `description_raw` exactly as printed — the bookkeeper will normalize the merchant name later.

**Brokerage / 401k / HSA holdings.** For each holding row: `symbol`, `description`, `shares` (decimal), `price` (per-share), `value`, `cost_basis` (if shown), `asset_class` (mapped from fund description: see playbook below).

**Mortgage activity.** Recent payments table: each entry with `date`, `total_paid_cents`, `principal_cents`, `interest_cents`, `escrow_cents`, `extra_principal_cents`.

**Tax forms.** Form-specific fields per playbook (W-2 boxes, 1099-DIV ordinary/qualified dividends, 1098 mortgage interest paid, 5498-SA HSA contributions).

### Step 7 — Normalize

- Money: `$1,247.50` → `124750` cents. Negative outflow: `-$87.42` → `-8742`.
- Dates: `01/15/26` → `2026-01-15`. Two-digit years assume current century unless context says otherwise.
- Trim whitespace from descriptions; preserve case.

### Step 8 — Confidence scoring

Per field, assign a confidence in `[0.0, 1.0]`:
- `1.0` — extracted directly from a labeled field.
- `0.85–0.95` — extracted from positional context (column header inferred, value clear).
- `0.6–0.8` — inferred from surrounding text or partially OCR'd.
- `< 0.6` — flag for human review.

The overall record gets `confidence = min(per-field confidences for required fields)`.

## Document type playbooks

### checking_statement / savings_statement

Required: institution, mask, period_start, period_end, opening_balance_cents, closing_balance_cents, transactions[]. Reconcile: `opening + Σ(transactions.amount) = closing` within ±$1.

### credit_card_statement

Required: institution, mask, period_start, period_end, opening_balance_cents, closing_balance_cents, statement_balance_cents, minimum_payment_cents, payment_due_date, transactions[]. Note: credit-card outflows from the cardholder's perspective are *purchases* (positive on the card balance) — but for the unified household ledger, treat purchases as **negative** (money leaving the household) and payments to the card as **positive** (or as internal transfers). Document the sign convention you used.

### brokerage_statement

Required: institution, mask, account_type, period_end, total_value_cents, holdings[]. Optional: cash_cents, transactions[] (buys/sells/dividends). Asset-class mapping:

| Fund hint | asset_class |
|---|---|
| "S&P 500", "Total Stock Market", "Large Cap Blend", VOO/VTI/SPY | `us_equity` |
| "International", "Developed Markets", "Emerging Markets", VXUS/IXUS | `intl_equity` |
| "Total Bond", "Aggregate Bond", "Treasury", BND/AGG | `us_bond` |
| "Money Market", "Cash Reserve", SPAXX | `cash` |
| "REIT", "Real Estate" | `reit` |
| "Target Date 20XX" | `target_date` (note the year) |

If unmappable, leave `asset_class: null` with confidence `0.5`.

### 401k_statement

Required: institution, owner, period_end, total_balance_cents, ytd_contribution_cents (if shown), employer_match_ytd_cents (if shown), holdings[]. Watch for vesting columns — record `vested_cents` separately if shown.

### hsa_statement

Required: institution, period_end, cash_cents, invested_cents, total_cents, ytd_contribut

Related in Writing & Docs