# PFM — Personal Finance Manager A self-hosted personal finance web application. Track income, expenses, investments, budgets, and savings goals. AI-powered financial assistant and receipt OCR built in. Sync bank accounts via Teller, Plaid, or Schwab. **Live at:** https://pfm.ngodanguyen.tech --- ## Table of Contents 1. [Features Overview](#features-overview) 2. [Getting Started](#getting-started) 3. [Dashboard](#dashboard) 4. [Transactions](#transactions) 5. [Accounts](#accounts) 6. [Categories](#categories) 7. [Budgets](#budgets) 8. [Goals & Savings](#goals--savings) 9. [Investments](#investments) 10. [AI Assistant](#ai-assistant) 11. [Reports & Export](#reports--export) 12. [Settings](#settings) 13. [Security & 2FA](#security--2fa) 14. [Recurring Transactions](#recurring-transactions) 15. [Bank Statement Import](#bank-statement-import) 16. [Receipt OCR](#receipt-ocr) 17. [Teller Bank Sync](#teller-bank-sync) 18. [Schwab Bank Sync](#schwab-bank-sync) 19. [Plaid Bank Sync](#plaid-bank-sync) 20. [USD → VND Rate Widget](#usd--vnd-rate-widget) 21. [System Logs](#system-logs) 22. [Keyboard Shortcuts & Tips](#keyboard-shortcuts--tips) --- ## Features Overview | Module | What it does | |--------|-------------| | **Dashboard** | Net worth, savings rate, cash flow chart, budget gauges, upcoming bills, reconcile, AI insight, USD/VND rate | | **Transactions** | Income/expense entry, transfer, inline category, bulk actions, quick date filters, duplicate detection, split transaction, filtered export | | **Accounts** | Multiple account types, auto-calculated or provider-synced balances (Teller / Plaid / Schwab) | | **Categories** | Custom expense + income categories with color and icon | | **Budgets** | Monthly spending limits with progress bars, budget vs actual chart, rollover support | | **Goals** | Savings goals with contribution tracking, projected completion, emergency fund tracker | | **Investments** | Stock/ETF/crypto portfolio, per-account view, price alerts, price history chart, live price fetch | | **AI Assistant** | Chat with your finances via Groq (free), daily auto-insight, streaming responses | | **Receipt OCR** | Drag-drop a receipt image — AI extracts and fills the form | | **Reports** | Monthly/quarterly/yearly summaries, net worth history, category spending trends, PDF/CSV/Excel export | | **Teller** | Live US bank sync (mTLS), balance refresh, transaction import, webhook | | **Schwab** | Schwab brokerage sync — balance, stock/ETF positions, transaction import, daily auto-sync | | **Plaid** | 12,000+ US banks/credit unions via Plaid — cursor sync, credit card billing, webhook auto-import | | **Bank Import** | Upload PDF/OFX/QFX/CSV bank statements — auto-categorizes, preview before import | | **Security** | TOTP 2FA, session idle timeout, rate limiting, audit log, at-rest encryption for bank tokens | | **System Logs** | DB-backed application log viewer with colour-coded levels, search, module filter, and purge | --- ## Getting Started ### First Login 1. Navigate to `https://pfm.ngodanguyen.tech` 2. Enter the username and password created during setup (`python scripts/init_db.py`) 3. Check "Remember me" to stay logged in across browser sessions ### Recommended Setup Order 1. **Add accounts** — add your bank accounts, cash wallet, and credit cards first 2. **Review categories** — 21 default categories are seeded; add custom ones if needed 3. **Set your currency** — go to Settings → Profile and pick from 8 currency options 4. **Connect bank sync** — connect Teller (US banks), Plaid (12,000+ institutions), or Schwab for automatic data 5. **Add transactions** — enter income/expenses manually, import a bank statement, or sync from a provider 6. **Set budgets** — once you have spending data, set monthly limits per category 7. **Create goals** — add savings goals and start contributing 8. **Add investments** — manually or via Schwab sync 9. **Enable 2FA** — recommended: Settings → Security → Two-Factor Authentication --- ## Dashboard The dashboard is the home screen — accessible from the sidebar or by clicking the PFM logo. ### Period Selector Three buttons in the top-right: **This Month**, **Last Month**, **Custom** (date range picker). All summary cards update for the chosen period. ### Summary Cards - **Income** — total income for the selected period - **Expenses** — total expenses for the selected period - **Net Cash Flow** — income minus expenses - **Savings Rate** — net cash flow ÷ income (green ≥ 20%, blue > 0%, red negative) - **Net Worth** — total assets minus liabilities - **Checking & Savings** — sum of checking, savings, and cash account balances - **Investments** — sum of investment and crypto account balances ### Reconcile Button Click **Reconcile** (next to period selector) to recalculate income, expenses, net cash flow, and savings rate while **excluding internal transfers**. This shows your true external cash flow. A notice shows the excluded transfer amounts and which categories were excluded. Click again to toggle back to the original totals. ### Cash Flow Chart Bar chart showing the last 6 months of income (green) vs expenses (red). ### Upcoming Bills Table of recurring rules due within the next 14 days — category icon, rule name, due date (color-coded: today/overdue in red, within 3 days in amber), amount, and account. Links to the Recurring page. ### Accounts Panel Lists all active accounts with current balances. Credit cards show **Amount Owed** (positive) instead of a raw negative balance. ### AI Daily Insight Auto-generated summary of your finances at midnight. Click **Generate Now** for an on-demand insight. Click **Open AI →** for the full chat interface. ### USD → VND Widget Reference-only exchange rate. Click ↻ to refresh without reloading. Click the widget to expand a 30-day trend chart. A ⚠ stale indicator appears if the rate is older than one day. ### Schwab Expiry Warning A banner appears on the dashboard when your Schwab refresh token expires within 2 days. Click the link to reconnect. --- ## Transactions ### Viewing Transactions Navigate via **Transactions** in the sidebar. Two tabs: **Expenses** and **Income** (with total counts). ### Filtering - **Search** — matches description and notes fields - **Category** dropdown - **Account** dropdown - **Date range** — from/to pickers - **Amount range** — min/max amount - **Quick date filters** — "This Month" and "Last Month" buttons above the filter bar; active button is highlighted; ✕ clears the quick filter - **Saved filter presets** — save the current filter combination under a name; reload it from the dropdown in one click ### Plaid Review Banner When transactions are imported via Plaid webhook without a category, a purple banner at the top of the Transactions page shows the count and a link to review and categorize them. The sidebar Transactions link also shows a badge with the count. ### Adding a Transaction Use the topbar **Income** / **Expense** buttons, or use the sidebar links. The form includes: - Type toggle (Income / Expense) — switches available categories - Description, amount, date, account, category, notes - **Duplicate detection** — if the same amount on the same date already exists, a yellow warning banner appears before you save ### Inline Category Change Click the **category dropdown** on any transaction row in the table to change the category. The change saves via AJAX — no page reload. ### Editing and Deleting Click **Edit** to open the full form. Click **Del** to delete permanently. ### Split Transaction Click the ✂ (scissors) icon on any transaction row to split it into multiple parts: - A split page shows the original transaction details and two default rows - Assign a different **category**, **description**, and **amount** to each part - A live **Remaining** counter shows how much is left to allocate - Add or remove rows as needed; the total must equal the original amount - On confirm, the original transaction is replaced by the individual split transactions ### Bulk Actions Check one or more transaction rows (or use the **select all** header checkbox) to reveal the bulk action toolbar: - **Set category** — apply a category to all selected rows at once; dropdowns update in the table without reload - **Delete** — delete all selected transactions after a confirmation dialog ### Export Filtered View In the filter bar, **CSV** and **Excel** buttons export the current filtered view (respects all active filters — search, category, account, date range, amount range). Exports are streamed for memory efficiency — no row limit. ### Transfers Click **Transfer** in the topbar. Creates a single transfer record between two accounts. Transfers are excluded from income/expense totals. --- ## Accounts ### Account Types `checking` / `savings` / `cash` / `credit_card` / `crypto` / `investment` / `other` ### Balance Sources - **Manually managed** — balance is computed from transactions (income − expenses ± transfers). Updates after every transaction. - **Teller-linked** — live balance from Teller API. Blue **Teller** badge. Updated after sync or Refresh click. - **Schwab-linked** — balance from Schwab snapshot (liquidation value). Green **Schwab** badge. Updated after Balance & Positions click or daily auto-sync. - **Plaid-linked** — live balance from Plaid API. Purple **Plaid** badge. Updated after sync or Refresh click. ### Teller Account Actions | Button | What it does | |--------|-------------| | **Refresh** | Pulls live balance from Teller API (AJAX) | | **Sync** | Opens transaction preview — import new transactions | | **Reset** | Clears sync cursor — next Sync re-fetches full 90-day history | ### Schwab Account Actions | Button | What it does | |--------|-------------| | **Balance & Positions** | Fetches live balance + investment holdings from Schwab | | **Transactions** | Opens transaction import preview | ### Plaid Account Actions | Button | What it does | |--------|-------------| | **Refresh** | Pulls live balance from Plaid API (AJAX) | | **Sync** | Opens transaction preview — import new transactions | | **Billing** | Fetches credit card due date, minimum payment, and statement balance | ### Plaid Credit Card Billing Card For Plaid-linked credit cards, a billing card on the account shows: due date, days remaining, minimum payment, and last statement balance. Updated by clicking **Billing**. ### Credit Cards Credit cards show two values: - **Amount Owed** — how much you currently owe (positive number) - **This Month** — expenses charged to the card this calendar month ### Opening Balance When creating a new account, enter the current balance in the **Opening Balance** field. For credit cards, enter a **negative number** to indicate existing debt (e.g. `-500` if you owe $500). --- ## Categories Navigate via the sidebar footer → **Categories**. **Default Expense (14):** Housing, Food & Dining, Transport, Utilities, Health, Entertainment, Shopping, Education, Insurance, Personal Care, Travel, Subscriptions, Gifts, Other **Default Income (7):** Salary, Freelance, Business, Investment, Rental, Gift Received, Other Income System categories (grey badge) cannot be deleted — color and icon can still be changed. Custom categories can be fully edited and deleted. --- ## Budgets Navigate via **Budgets** in the sidebar. Use ◀ ▶ to navigate months. ### Setting Budgets Click **Add Budget** in the topbar to set a monthly limit for a category. Click **Edit** on an existing row to adjust. ### Budget vs Actual Chart A horizontal bar chart at the top of the page shows **Spent** (color-coded green/amber/red) vs **Budget limit** per category — for all categories that have a budget set this month. ### Progress Bars Each row shows a progress bar: green (< 80%) → amber (80–99%) → red (≥ 100%). Rows that exceed the limit have a red background tint. ### Rollover Enable rollover on a budget to carry unused amounts forward to the next month. A blue **+rollover** badge shows the carried-over amount on the row. ### Copy from Previous Month Click **Copy from [month]** at the top right to duplicate last month's budgets to the current month in one click. Also available on the empty-state screen. --- ## Goals & Savings Navigate via **Goals** in the sidebar. - Create goals with target amount, target date, color, and icon - Add contributions with dates and notes; view contribution history - Progress bar + projected completion date (based on average monthly contribution) - Auto-completes when 100% is reached - **Emergency Fund Tracker** — shows how many months your liquid assets (checking + savings + cash accounts) cover at your current monthly spending rate, with 3-month and 6-month targets --- ## Investments Navigate via **Investments** in the sidebar. ### Portfolio Overview Summary cards: **Total Value**, **Total Cost**, **Unrealized P&L**, **Return %**. Allocation doughnut chart by asset type. Sidebar "By Account" breakdown. ### Price Alerts A yellow banner at the top of the Investments page appears when any holding moves ≥ 5% intraday. The banner lists each mover with its ticker and change percentage. Dismiss with ×. Alerts are recalculated daily when prices update. ### Investment Detail Page Click any holding to see the detail page: - Current price, day change, P&L - **Price history chart** — 1W / 1M / 3M / 6M / 1Y timeframes; fetched from Yahoo Finance - Full buy/sell/dividend/split transaction log ### Per-Account View If investments span multiple accounts (e.g. Schwab Individual + Schwab Roth IRA), holdings are grouped into separate sections — one per account. Each section shows the account name, total value, and its own holdings table. Same ticker in different accounts stays as separate rows. ### Syncing from Schwab Click **Sync Schwab** in the topbar to pull the latest holdings and balances for all mapped Schwab accounts at once. ### Adding a Holding Manually Click **Add Holding** in the topbar. After saving, go to the detail page to record buy transactions. ### Ticker Format - US Stocks/ETFs: `AAPL`, `VOO`, `QQQ` - Crypto: `BTC-USD`, `ETH-USD` - International: Yahoo Finance suffix (e.g. `VIC.VN`) ### Refreshing Prices Click **Refresh Prices** in the topbar. Prices also auto-update daily at 4 PM weekdays via cron. --- ## AI Assistant Navigate via **AI Assistant** in the sidebar. The AI has access to the last 90 days of transactions, budget status, goals, investments, and net worth. No personal names or identifiers are sent — only aggregated financial figures. - Type a message and press **Enter** to send; **Shift+Enter** for a new line - Responses stream word-by-word (SSE) - Eight quick-question suggestion buttons - **Daily Insight** — auto-generated at midnight; click **Generate Now** to create on demand - **Chat History** — all past responses with timestamps ### AI Model Change in Settings → Profile → AI Model: - `llama-3.3-70b-versatile` — best quality (default) - `llama-3.1-8b-instant` — faster, slightly lower quality --- ## Reports & Export Navigate via **Reports** in the sidebar. Four tabs: **Monthly**, **Quarterly**, **Yearly**, **Tax Year**. Export buttons in the topbar: - **CSV** — plain text, streamed (no row limit) - **Excel** — color-coded (green = income, red = expense), formatted, streamed - **PDF** — printable report via WeasyPrint **Net Worth History** — line chart from monthly snapshots. **Snapshot Now** saves today's values immediately. **Category Spending Trends** — top 6 categories over the last 6 months. --- ## Settings Navigate via the gear icon at the bottom of the sidebar. | Section | What you can change | |---------|-------------------| | **Profile** | Display name, email, timezone, currency (8 options), AI model | | **Password** | Change password (requires current password) | | **Security** | Enable/disable TOTP 2FA; view Audit Log | | **Recurring** | Create/edit/pause recurring transaction rules | | **Import** | Upload CSV or bank statement for bulk import | | **System Logs** | View, search, and purge application logs | All Settings sub-pages have a **← Settings** back button in the topbar. --- ## Security & 2FA ### Two-Factor Authentication (TOTP) 1. Go to Settings → **Security** section 2. Click **Enable Two-Factor Authentication** 3. Scan the QR code with an authenticator app (Google Authenticator, Authy, etc.) or enter the manual key 4. Enter the 6-digit code to confirm setup 5. On future logins, you will be prompted for a 6-digit code after password entry To disable 2FA: Settings → Security → **Disable** (requires password confirmation). The TOTP verify endpoint is rate-limited (10/min, 30/hr). After 5 failed attempts in one session, the login is invalidated and you must start over. ### Session Idle Timeout Sessions expire after 60 minutes of inactivity (configurable via `SESSION_IDLE_MINUTES` in `.env`). ### Audit Log Settings → **Audit Log** shows a paginated, filterable log of security events with IP addresses and timestamps: - Login success / failure (with and without 2FA) - 2FA enabled / disabled - Password changed - Schwab connected / disconnected Filter by event type. **Purge** dropdown removes entries older than 7, 30, or 90 days. ### At-Rest Encryption Teller, Schwab, and Plaid OAuth tokens are encrypted in the database using AES-128 (Fernet). Changing `SECRET_KEY` in `.env` invalidates stored tokens — all providers would need to reconnect. --- ## Recurring Transactions Navigate via Settings → **Recurring**. Rules auto-create transactions on a schedule. Frequencies: `daily`, `weekly`, `biweekly`, `monthly`, `quarterly`, `yearly`. - Set start date, optional end date, account, category, and amount - Pause/enable individual rules without deleting them - **Run Now** button processes all overdue rules immediately - **Upcoming (30 days)** panel shows next-due dates for all active rules - Rules also appear on the **Dashboard upcoming bills widget** when due within 14 days --- ## Bank Statement Import Navigate via Settings → **Import Statement** (sidebar) or Settings → Import. ### Supported Formats | Format | Notes | |--------|-------| | **PDF** | Digital (text-based) PDFs only — scanned/image PDFs are rejected. Uses table extraction first; falls back to AI parsing via Groq if no structured table is found | | **OFX / QFX** | Standard Open Financial Exchange format (most US banks) | | **CSV** | Chase, BofA, Citi, Capital One, Discover, Amex, USAA, Wells Fargo, and Generic CSV | | **Custom CSV** | Map your own column headers when the bank format isn't recognized | ### Import Flow 1. Upload the file 2. **Preview table** — per-row checkboxes, editable category dropdowns, bulk category apply, bulk type toggle 3. Click **Confirm Import** to write to the database ### Duplicate Detection - OFX/QFX: uses the `FITID` field - CSV/PDF: matches on date + amount + description + account ### Auto-Categorization 200+ keyword rules automatically assign categories to imported transactions. Override any row in the preview before confirming. --- ## Receipt OCR On the **new expense form**: drag-drop or click the purple **AI Receipt Scanner** panel to upload a receipt image (JPG/PNG/GIF/WEBP, max 10MB). The AI extracts amount, date, merchant name, and category suggestion, then fills the form fields with a green flash animation. Always review before saving. On the **edit form**: - **Re-extract** button re-runs OCR on the already-attached receipt - OCR also runs automatically when you select a new image file in the upload field --- ## Teller Bank Sync Teller connects US bank accounts using a secure mTLS connection. Accessible via **Settings** → Bank Connections. ### Connecting 1. Go to Settings → **Bank Connections** 2. Click **Connect a Bank** — the Teller Connect modal opens 3. Select your bank and log in 4. After connecting, use **Map Account** on any unmapped accounts to link them to PFM accounts ### Syncing Transactions On the **Accounts** page, find a Teller-linked account (blue **Teller** badge) and click **Sync**. Review the transaction preview and confirm to import. After import, the live balance is fetched from Teller automatically. ### Webhook Teller can push transaction updates to PFM automatically. The webhook endpoint verifies the `Teller-Signature` header (HMAC-SHA256) and has a 5-minute replay window. ### Disconnecting Settings → Bank Connections → **Disconnect**. Imported transactions are kept. --- ## Schwab Bank Sync Schwab integration uses OAuth 2.0 to sync brokerage and IRA accounts. Accessible via **Settings**. ### Connecting 1. Set `SCHWAB_CLIENT_ID`, `SCHWAB_CLIENT_SECRET`, `SCHWAB_REDIRECT_URI` in `.env` 2. Register the redirect URI in the Schwab developer portal (exact match required) 3. Settings → **Schwab** → **Connect Schwab Account** 4. Authorize on Schwab's login page 5. On the **Map Accounts** page, link each Schwab account to a PFM account (or create new ones) ### Syncing Balance & Investment Positions On the **Accounts** page, click **Balance & Positions** on a Schwab account (green badge). This: - Updates the account balance to Schwab's total portfolio liquidation value - Upserts stock/ETF/bond/fund holdings into the Investments section - Associates each holding with the specific account (Individual vs Roth IRA stay separate) Also click **Sync Schwab** in the Investments page topbar to update all Schwab accounts at once. ### Syncing Transactions Click **Transactions** on a Schwab account card. Review the preview and confirm to import. ### Token Expiry Schwab access tokens expire after 30 minutes (auto-refreshed). Refresh tokens last 7 days — a dashboard warning appears when ≤ 2 days remain. Reconnect via Settings → Schwab to reset the expiry. ### Daily Auto-Sync A cron job runs at 7 AM daily (`scripts/sync_schwab.py`) — updates balances, positions, and transactions for all mapped Schwab accounts automatically. ### Disconnecting Settings → Schwab → **Disconnect**. Imported transactions and holdings are kept. --- ## Plaid Bank Sync Plaid connects to 12,000+ US banks and credit unions. Accessible via **Settings**. ### Connecting 1. Set `PLAID_CLIENT_ID`, `PLAID_SECRET`, `PLAID_ENV` (`sandbox` or `production`) in `.env` 2. Settings → **Plaid** → **Connect Bank Account** 3. The Plaid Link widget opens — search for your institution and log in 4. On the **Map Accounts** page, link each Plaid account to a PFM account (or create new ones) ### Syncing Transactions On the **Accounts** page, click **Sync** on a Plaid-linked account (purple badge). This uses cursor-based sync (`/transactions/sync`) — only new transactions since the last sync are fetched. Review the preview and confirm. **Reset Sync** — click **Reset** to clear the sync cursor. The next sync re-fetches the full available history; duplicates are automatically skipped via the `Plaid:` marker in notes. ### Live Balance Refresh Click **Refresh** on a Plaid account card to fetch the latest balance from Plaid's `/accounts/balance/get` endpoint. ### Credit Card Billing For Plaid-linked credit cards, click **Billing** on the account card to fetch: - Next payment due date - Minimum payment amount - Last statement balance - Overdue indicator This information also appears in the billing card on the Accounts page. ### Webhook Auto-Import Plaid can push new transactions to PFM via webhook (`POST /plaid/webhook`). When a `TRANSACTIONS/SYNC` event arrives, new transactions are imported silently (no preview step). Transactions imported this way without a category appear in the **Plaid review** banner on the Transactions page and the sidebar badge — review and categorize them from there. The webhook is verified using Plaid's JWT signature (ES256). Configure the webhook URL in Settings → Plaid → **Apply to Existing Items**. ### Disconnecting Settings → Plaid → **Disconnect** on a connection. Imported transactions are kept. --- ## USD → VND Rate Widget Reference-only exchange rate on the dashboard. Does not affect any app calculations. - Click ↻ to force-refresh without reloading the page - Click the widget to expand a 30-day trend chart - Source label shown (yfinance / exchangerate-api) - ⚠ stale indicator appears if the cached rate is more than one day old --- ## System Logs Navigate via Settings → **System Logs** (or sidebar → Settings → System Logs card). Application log entries are stored in the database and shown in a colour-coded viewer: - Filter by level (INFO / WARNING / ERROR / DEBUG) or free-text search - Filter by module name (e.g. `app.teller`, `app.plaid`) - **Auto-refresh** toggle for live monitoring - **Download** — downloads the raw log file (`app.log`) - **Purge** dropdown — delete DB entries older than 7, 30, or 90 days - **Clear All** — truncates both the DB log table and the log file - DB entry count shown in the page header --- ## Keyboard Shortcuts & Tips ### Navigation - Sidebar collapses on desktop — click ☰ to toggle (state saved in localStorage) - On mobile, tap outside the sidebar to close it; tables scroll horizontally ### Transactions - **Enter** sends in AI chat; **Shift+Enter** adds a new line - Change category directly in the transactions table — no form needed - Date defaults to today — change it for past transactions - Use the **✂ Split** button on a row to divide a transaction across multiple categories - Export the current filtered view with the **CSV** / **Excel** buttons in the filter bar ### Budgets - **Copy from previous month** at the start of each month saves re-entering all budgets - The **Budget vs Actual** chart gives a quick visual of where you stand each month - Rollover amounts appear as a blue **+rollover** badge on the row ### Investments - Click any holding row to go to the detail page with P&L and price history chart - **↓** button on the new transaction price field fetches the live price for that ticker - Ticker symbols are auto-uppercased on save - Price alerts banner appears automatically when any holding moves ≥ 5% intraday ### Reports - **Snapshot Now** saves today's net worth to the history chart immediately - CSV and Excel exports from the Reports page use the selected period - From the Transactions page, use the filter bar export buttons to export any filtered view ### Security - Enable **2FA** in Settings → Security for additional login protection - The **Audit Log** (Settings → Security → Audit Log) records every login attempt and security event with IP address - Sessions time out after 60 minutes of inactivity