> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ACCOUNT TYPE SYSTEM

# Account Type System Guide

## Understanding Types, Subtypes, and Calculations for Plaid & SnapTrade

**Last Updated:** 2025-10-22\
**Related Docs:** `SNAPTRADE-ACCOUNTS-TABLE.md`, `SNAPTRADE-TYPE-MIGRATION.md`

***

## 📚 Table of Contents

1. [Overview](#overview)
2. [Core Concepts](#core-concepts)
3. [Plaid Account Type System](#plaid-account-type-system)
4. [SnapTrade Account Type System](#snaptrade-account-type-system)
5. [Type Mapping & Translation](#type-mapping--translation)
6. [User Overrides](#user-overrides)
7. [Calculations & Grouping](#calculations--grouping)
8. [The getEffectiveAccountType Function](#the-geteffectiveaccounttype-function)
9. [Data Flow Diagrams](#data-flow-diagrams)
10. [Common Pitfalls](#common-pitfalls)

***

## Overview

Portfolio Tracker integrates with two main financial data providers:

* **Plaid:** Bank accounts, credit cards, loans (traditional banking)
* **SnapTrade:** Brokerage accounts (investment platforms like Fidelity)

Both use a **two-tier categorization system** (Type + Subtype), but they arrived at this system differently.

***

## Core Concepts

### **Type vs Subtype**

**Type (Tier 1):** Broad category

* `investment` - Accounts with stocks, bonds, mutual funds
* `depository` - Cash accounts (checking, savings)
* `credit` - Credit cards
* `loan` - Loans, mortgages

**Subtype (Tier 2):** Specific account detail

* `401k`, `ira`, `roth` - Retirement account types
* `brokerage` - Taxable investment account
* `checking`, `savings` - Bank account types
* `credit card` - Credit card type

### **Why Two Tiers?**

* **Type** determines **where money goes in calculations**
  * Investment → Portfolio Value
  * Depository → Cash on Hand
  * Credit/Loan → Debts
* **Subtype** determines **how it's displayed and grouped**
  * 401k, IRA → Retirement group
  * Brokerage → Taxable Investments group

***

## Plaid Account Type System

### **How Plaid Works**

Plaid connects to banks and returns accounts with pre-assigned types and subtypes:

```json theme={null}
{
  "account_id": "abc123",
  "name": "My 401k",
  "type": "investment", // Plaid's categorization
  "subtype": "401k", // Plaid's specific type
  "balance": 50000
}
```

### **Plaid's Type Hierarchy**

```
investment
├── 401k
├── 403b
├── ira
├── roth
├── brokerage
└── ...

depository
├── checking
├── savings
├── money market
├── hsa
└── ...

credit
├── credit card
└── ...

loan
├── mortgage
├── auto
├── student
└── ...
```

### **Database Storage (Plaid)**

**Table:** `accounts`

| Column | Purpose | Example | Mutable? |
| - | - | - | - |
| `source_type` | Original Plaid type | `"investment"` | ❌ Immutable |
| `source_subtype` | Original Plaid subtype | `"401k"` | ❌ Immutable |
| `type` | Current type (user can override) | `"investment"` | ✅ User editable |
| `subtype` | Current subtype (user can override) | `"401k"` | ✅ User editable |

**Why both?**

* `source_*` = What Plaid said originally (never changes)
* `type/subtype` = What user wants it to be (can be changed)

***

## SnapTrade Account Type System

### **How SnapTrade Works**

SnapTrade returns brokerage accounts with a **single-tier** type system:

```json theme={null}
{
  "id": "xyz789",
  "name": "Fidelity 401k",
  "type": "401K", // SnapTrade's format (this is really a subtype!)
  "balance": 75000
}
```

**Problem:** SnapTrade's `"401K"` is specific (like a subtype), but we need a broad category (type) for calculations.

### **Our Solution: Map to Plaid Format**

We translate SnapTrade's single-tier system to Plaid's two-tier system:

```javascript theme={null}
// lib/account-type-mappings.ts
export const SNAPTRADE_TO_PLAID_MAPPING = {
  '401K': { type: 'investment', subtype: '401k' },
  IRA: { type: 'investment', subtype: 'ira' },
  ROTH: { type: 'investment', subtype: 'roth' },
  I: { type: 'investment', subtype: 'brokerage' },
  HSA: { type: 'depository', subtype: 'hsa' },
  // ... etc
}
```

### **Database Storage (SnapTrade)**

**Table:** `snaptrade_accounts`

| Column | Purpose | Example | Mutable? |
| - | - | - | - |
| `source_account_type` | Original SnapTrade type | `"401K"` | ❌ Immutable |
| `source_type` | **Mapped** Plaid-style type | `"investment"` | ❌ Immutable |
| `source_subtype` | **Mapped** Plaid-style subtype | `"401k"` | ❌ Immutable |
| `type` | Current type (user can override) | `"investment"` | ✅ User editable |
| `subtype` | Current subtype (user can override) | `"401k"` | ✅ User editable |
| `account_type` | **Legacy field** (mirrors `type`) | `"investment"` | ✅ Auto-synced |

**Note:** SnapTrade has 6 type-related columns (vs Plaid's 4) because we need to preserve the original SnapTrade format.

***

## Type Mapping & Translation

### **SnapTrade → Plaid Conversion**

**When:** During account creation (`process-connection/route.ts`)

```typescript theme={null}
// 1. Get SnapTrade account
const snapTradeType = account.type // "401K"

// 2. Map to Plaid format
const mapping = SNAPTRADE_TO_PLAID_MAPPING[snapTradeType]
// → { type: "investment", subtype: "401k" }

// 3. Store in database
await supabase.from('snaptrade_accounts').insert({
  source_account_type: '401K', // Original
  source_type: 'investment', // Mapped
  source_subtype: '401k', // Mapped
  type: 'investment', // Current (starts same as source)
  subtype: '401k', // Current (starts same as source)
  account_type: 'investment', // Legacy (mirrors type)
})
```

### **Why This Matters**

**Before mapping:**

```javascript theme={null}
// ❌ Can't do this - every account type is different!
if (
  account.account_type === '401K' ||
  account.account_type === 'IRA' ||
  account.account_type === 'ROTH'
) {
  // Add to investments
}
```

**After mapping:**

```javascript theme={null}
// ✅ Simple and unified!
if (account.type === 'investment') {
  // Add to investments (works for ALL investment accounts)
}
```

***

## User Overrides

### **Why Allow Overrides?**

Sometimes categorizations are wrong:

* Plaid might mark a brokerage account as "depository"
* User might want to categorize differently for personal reasons
* Institution might not provide accurate account types

### **How Overrides Work**

**1. User Changes Type:**

```
API: app/api/accounts/update-type/route.ts
     Updates: type = "depository", subtype = "checking"
     Preserves: source_type = "investment", source_subtype = "401k"

Note: UI for account type override is not yet implemented.
      The backend API is ready, but no frontend dialog exists currently.
```

**2. Detection:**

```typescript theme={null}
// lib/account-type-utils.ts
const hasUserOverride = account.source_type
  ? account.type !== account.source_type ||
    account.subtype !== account.source_subtype
  : false

// If TRUE: User changed it
// If FALSE: Still using original categorization
```

**3. Reset to Default:**

```typescript theme={null}
// Copy source values back to current
account.type = account.source_type
account.subtype = account.source_subtype
```

### **Override Detection Example**

```javascript theme={null}
// Scenario A: No override
{
  source_type: "investment",
  type: "investment",
  source_subtype: "401k",
  subtype: "401k"
}
// hasUserOverride = FALSE (type === source_type)

// Scenario B: User override
{
  source_type: "investment",  // What it originally was
  type: "depository",         // What user changed it to
  source_subtype: "401k",
  subtype: "checking"
}
// hasUserOverride = TRUE (type !== source_type)
```

***

## Calculations & Grouping

### **How Accounts Are Categorized**

**Primary Rule:** Use the `type` field to determine category

```typescript theme={null}
// Daily Snapshot Cron (simplified)
if (account.type === 'investment') {
  totalInvestments += account.balance
} else if (account.type === 'depository') {
  totalCash += account.balance
} else if (account.type === 'credit' || account.type === 'loan') {
  totalDebts += account.balance
}
```

### **Net Worth Calculation**

```
Assets:
├── Total Investments (type = 'investment')
│   ├── Plaid investment accounts
│   ├── SnapTrade investment accounts
│   └── Manual investment accounts
│
├── Total Cash (type = 'depository')
│   ├── Plaid checking/savings
│   ├── SnapTrade HSA/cash accounts
│   └── Manual cash accounts
│
└── Other Assets
    ├── Properties
    ├── Vehicles
    └── Crypto

Liabilities:
├── Credit Cards (type = 'credit')
├── Loans (type = 'loan')
└── Mortgages (type = 'loan', subtype = 'mortgage')

Net Worth = Total Assets - Total Liabilities
```

### **Account Grouping in UI**

**By Type:**

* **Retirement:** `subtype` includes retirement keywords (401k, ira, roth, etc.)
* **Investments:** `type = 'investment'` AND not retirement
* **Cash:** `type = 'depository'`
* **Credit Cards:** `type = 'credit'`
* **Loans:** `type = 'loan'`

**Example Code:**

```typescript theme={null}
// components/account-list-view.tsx
const retirementAccounts = accounts.filter((account) => {
  // For all accounts, check if subtype is retirement-related
  return retirementSubtypes.some(
    (st) => st.toLowerCase() === account.subtype?.toLowerCase()
  )
})

const investmentAccounts = accounts.filter((account) => {
  return (
    getEffectiveAccountType(account) === 'investment' && !isRetirement(account)
  )
})
```

***

## The getEffectiveAccountType Function

### **Purpose**

Determines the **actual type** to use for calculations, handling:

1. User overrides (always respected)
2. Edge cases (depository with investments → treat as investment)
3. Backward compatibility

### **Location**

`lib/account-type-utils.ts`

### **Logic Flow**

```typescript theme={null}
export function getEffectiveAccountType(account: any): string {
  // Step 1: Guard against null
  if (!account) return 'unknown'

  // Step 2: Get the current type
  const accountType = account.type || account.account_type || 'unknown'

  // Step 3: Check if user overrode the type
  const hasUserOverride = account.source_type
    ? account.type !== account.source_type ||
      account.subtype !== account.source_subtype
    : false

  // Step 4: If user override, ALWAYS respect it
  if (hasUserOverride) {
    return accountType // Use what user chose
  }

  // Step 5: Handle edge case - depository with investments
  // (e.g., Merrill Edge checking account that holds stocks)
  if (accountType === 'depository' && account.positions?.length > 0) {
    return 'investment' // Auto-correct
  }

  // Step 6: Return original type
  return accountType
}
```

### **Why This Matters**

**Without `getEffectiveAccountType()`:**

```typescript theme={null}
// ❌ Miss edge cases
if (account.type === 'depository') {
  totalCash += account.balance
  // But what if it has stocks? Wrong category!
}
```

**With `getEffectiveAccountType()`:**

```typescript theme={null}
// ✅ Handles all cases
const effectiveType = getEffectiveAccountType(account)
if (effectiveType === 'depository') {
  totalCash += account.balance // Only true cash accounts
}
```

### **Usage Across Codebase**

**Daily Snapshots:**

```typescript theme={null}
// app/api/cron/process-daily-snapshot/route.ts
const effectiveType = getEffectiveAccountType(account)
if (effectiveType === 'investment') {
  totalMarketValue += account.balance
}
```

**Real-Time Calculations:**

```typescript theme={null}
// hooks/queries/use-accounts.ts
const totalCash = accounts.reduce((sum, account) => {
  const effectiveType = getEffectiveAccountType(account)
  if (effectiveType === 'depository') {
    return sum + account.balance_current
  }
  return sum
}, 0)
```

***

## Data Flow Diagrams

### **Plaid Account Creation**

```
Plaid API → process-connection
    ↓
    Saves to database:
    ├── source_type (from Plaid)
    ├── source_subtype (from Plaid)
    ├── type (copy of source_type)
    └── subtype (copy of source_subtype)
    ↓
    Used in calculations:
    └── getEffectiveAccountType(account)
        → Returns account.type (or auto-corrects edge cases)
```

### **SnapTrade Account Creation**

```
SnapTrade API → process-connection
    ↓
    Receives: type = "401K"
    ↓
    Map via SNAPTRADE_TO_PLAID_MAPPING:
    "401K" → { type: "investment", subtype: "401k" }
    ↓
    Saves to database:
    ├── source_account_type = "401K" (original)
    ├── source_type = "investment" (mapped)
    ├── source_subtype = "401k" (mapped)
    ├── type = "investment" (current)
    ├── subtype = "401k" (current)
    └── account_type = "investment" (legacy, mirrors type)
    ↓
    Used in calculations:
    └── getEffectiveAccountType(account)
        → Returns account.type (respects overrides)
```

### **User Override Flow**

```
User clicks "Edit Account Type"
    ↓
UI: account-type-dialog.tsx
    Shows: Current (type/subtype)
    Shows: Original (source_type/source_subtype)
    ↓
User selects: "Depository / Checking"
    ↓
API: /api/accounts/update-type
    Updates:
    ├── type = "depository"
    ├── subtype = "checking"
    Preserves:
    ├── source_type = "investment"
    └── source_subtype = "401k"
    ↓
Next calculation:
    getEffectiveAccountType(account)
    ├── Detects: type !== source_type
    ├── hasUserOverride = TRUE
    └── Returns: "depository" (respects user choice)
```

***

## Common Pitfalls

### **❌ Pitfall 1: Hardcoding SnapTrade Types**

```typescript theme={null}
// DON'T DO THIS:
if (account.account_type === '401K' || account.account_type === 'IRA') {
  // This breaks after migration!
}

// DO THIS:
if (account.type === 'investment') {
  // Works forever!
}
```

### **❌ Pitfall 2: Forgetting Fallbacks**

```typescript theme={null}
// DON'T DO THIS:
const accountType = account.type // Might be undefined!

// DO THIS:
const accountType = account.type || account.account_type || 'unknown'
```

### **❌ Pitfall 3: Not Using getEffectiveAccountType**

```typescript theme={null}
// DON'T DO THIS:
if (account.type === 'depository') {
  totalCash += account.balance
  // Misses edge cases!
}

// DO THIS:
const effectiveType = getEffectiveAccountType(account)
if (effectiveType === 'depository') {
  totalCash += account.balance
}
```

### **❌ Pitfall 4: Comparing Type to source\_account\_type**

```typescript theme={null}
// DON'T DO THIS (for SnapTrade):
const hasOverride = account.account_type !== account.source_account_type
// "investment" !== "401K" → Always TRUE (false positive!)

// DO THIS:
const hasOverride = account.type !== account.source_type
// "investment" !== "investment" → FALSE (correct!)
```

### **❌ Pitfall 5: Ignoring Hidden Accounts**

```typescript theme={null}
// DON'T DO THIS:
const total = accounts.reduce((sum, a) => sum + a.balance, 0)
// Includes hidden accounts!

// DO THIS:
const total = accounts
  .filter((a) => !a.is_hidden)
  .reduce((sum, a) => sum + a.balance, 0)
```

***

## Quick Reference

### **Key Files**

| File | Purpose |
| - | - |
| `lib/account-type-mappings.ts` | SnapTrade → Plaid mapping |
| `lib/account-type-utils.ts` | `getEffectiveAccountType()` logic |
| `app/api/cron/process-daily-snapshot/route.ts` | Daily calculations |
| `hooks/queries/use-accounts.ts` | Real-time calculations |
| `app/api/accounts/update-type/route.ts` | User override handling (API only) |

### **Key Database Tables**

| Table | Source | Columns |
| - | - | - |
| `accounts` | Plaid | `source_type`, `source_subtype`, `type`, `subtype` |
| `snaptrade_accounts` | SnapTrade | `source_account_type`, `source_type`, `source_subtype`, `type`, `subtype`, `account_type` |
| `manual_accounts` | User | `account_type`, `account_subtype` |

### **Type Values (Tier 1)**

* `investment` - Investment accounts
* `depository` - Cash/bank accounts
* `credit` - Credit cards
* `loan` - Loans, mortgages
* `other` - Other account types

### **Common Subtypes (Tier 2)**

**Investments:**

* `401k`, `403b`, `457b` - Employer retirement plans
* `ira`, `roth`, `sep` - Individual retirement accounts
* `brokerage` - Taxable investment account
* `529` - Education savings

**Depository:**

* `checking` - Checking account
* `savings` - Savings account
* `money market` - Money market account
* `hsa` - Health savings account

**Loans:**

* `mortgage` - Home mortgage
* `auto` - Car loan
* `student` - Student loan

***

## Testing Your Understanding

### **Quiz 1:** What type/subtype would these be?

1. Fidelity 401(k) (SnapTrade) → ?
2. Chase Checking (Plaid) → ?
3. Vanguard Roth IRA (SnapTrade) → ?
4. Capital One Credit Card (Plaid) → ?

**Answers:**

1. `type: "investment"`, `subtype: "401k"`
2. `type: "depository"`, `subtype: "checking"`
3. `type: "investment"`, `subtype: "roth"`
4. `type: "credit"`, `subtype: "credit card"`

### **Quiz 2:** Will this account be in the retirement group?

```javascript theme={null}
{
  type: "investment",
  subtype: "401k",
  source_type: "investment",
  source_subtype: "401k"
}
```

**Answer:** YES (subtype is "401k", which is in retirement subtypes list)

### **Quiz 3:** Does this account have a user override?

```javascript theme={null}
{
  type: "depository",
  subtype: "checking",
  source_type: "investment",
  source_subtype: "401k"
}
```

**Answer:** YES (`type !== source_type`)

***

## Migration History

### **Before Migration (Old System)**

SnapTrade accounts:

```javascript theme={null}
{
  account_type: "401K",  // Single field, SnapTrade format
}
```

Problems:

* Can't easily group by category
* Hardcoded type checks everywhere
* No user override support
* Inconsistent with Plaid

### **After Migration (New System)**

SnapTrade accounts:

```javascript theme={null}
{
  source_account_type: "401K",    // Original preserved
  source_type: "investment",      // Mapped category
  source_subtype: "401k",         // Mapped specific type
  type: "investment",             // Current (user can change)
  subtype: "401k",                // Current (user can change)
  account_type: "investment"      // Legacy (auto-synced)
}
```

Benefits:

* ✅ Unified with Plaid
* ✅ Easy category grouping (`type === 'investment'`)
* ✅ User override support
* ✅ Historical tracking (snapshots save type/subtype)
* ✅ Backward compatible (old code still works with fallbacks)

***

## Summary

1. **Both Plaid and SnapTrade** use a **two-tier system** (Type + Subtype)
2. **Type** determines **calculations**, **Subtype** determines **display**
3. **source\_\*** columns are **immutable** (what provider said)
4. **type/subtype** columns are **mutable** (user can override)
5. **Always use** `getEffectiveAccountType()` for calculations
6. **User overrides** are detected by comparing `type` to `source_type`
7. **SnapTrade accounts** get mapped from SnapTrade format → Plaid format
8. **Backward compatibility** via fallbacks: `account.type || account.account_type`

***

**Questions?** See:

* `SNAPTRADE-ACCOUNTS-TABLE.md` - Detailed SnapTrade column reference
* `CRITICAL-VERIFICATION-CHECKLIST.md` - Calculation path audit
* `lib/account-type-mappings.ts` - Full mapping table


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.