run_read_query
Run a single Postgres SELECT against the user's data. The database automatically scopes results to the current user — do NOT add WHERE user_id clauses. For net worth, budget, or asset valuation use the dedicated tools instead. Provide a one-line rationale explaining the query.
IMPORTANT - money units: columns the schema below marks as minor units (the marker reads roughly "[minor units - divide by 100]") are stored as INTEGER minor units - the real amount x 100, for ALL currencies including IDR with no decimal places (e.g. Rp 2,500,000 is stored as 250000000). Divide ONLY those marked columns by 100.0 in your SELECT projection to get real amounts, e.g. SELECT SUM(amount) / 100.0 AS total. Do NOT divide columns that are already in major units - in particular the v_chat_transaction view's amount_major and signed_amount are already real major-unit amounts, so use them directly (they are the easiest correct way to sum spend/income). Never multiply by 100.
asset
purchase_price is the per-unit purchase price in purchase_currency rows with deleted_at IS NOT NULL are soft-deleted; exclude them for per-asset P&L questions the getAssets tool already returns cost basis, P&L, and P&L % in the user's base currency; prefer it over hand-rolled SQL
id: string
user_id: string
account_id: string (nullable)
type: string [enum: crypto | stock | real_estate | vehicle | commodity | bond | other]
symbol: string (nullable)
display_symbol: string (nullable)
quantity: string
unit: string (nullable)
purchase_price: number (nullable) [minor units — divide by 100]
purchase_currency: string (nullable)
valuation_mode: string [enum: auto | manual]
created_at: date
updated_at: date
deleted_at: date (nullable)
asset_split
id: string
asset_id: string
effective_date: string
ratio_from: number
ratio_to: number
source: string [enum: manual | yahoo]
status: string [enum: pending | applied | dismissed]
cache_applied: boolean
created_at: date
updated_at: date
asset_transaction
per-trade buy/sell record for an asset; join transaction on transaction_id for date, type (buy | sell), currency, and soft-delete status (exclude deleted_at IS NOT NULL) price_per_unit is in the joined transaction's currency an asset conversion (swap of one asset for another) appears as a paired sell + buy on the same date whose transactions have account_id IS NULL; they move no cash (v_chat_transaction reports them as neutral with signed_amount 0)
id: string
transaction_id: string
asset_id: string
quantity: string
price_per_unit: number [minor units — divide by 100]
created_at: date
category
id: string
user_id: string
name: string
icon: string (nullable)
parent_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
counterparty
id: string
user_id: string
name: string
notes: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
envelope
this table represents what the user calls a 'budget' — the DB table is named
envelopefor historical reasons, but always say 'budget' in user-facing replies budgets are buckets that group categories — a user mentioning a budget by name (e.g., 'dating', 'vacation') almost always means a row here, not a category v_chat_transaction.envelope_name resolves the (transaction → budget) link for you; prefer that over joining envelope_category yourself
id: string
user_id: string
name: string
budgeted_amount: number [minor units — divide by 100]
currency: string
period: string [enum: weekly | monthly]
tracking_mode: string [enum: category | manual]
include_investments: boolean
category_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
envelope_category
junction table linking categories to budgets; v_chat_transaction.envelope_name already follows this path, so you rarely need to query envelope_category directly
id: string
envelope_id: string
category_id: string
exchange_rate
stored one direction per pair (typically from_currency = USD); for the reverse direction, use 1/rate from the existing row rather than expecting an explicit inverse row use the latest row per (from_currency, to_currency) pair (order by fetched_at DESC, limit 1)
id: string
from_currency: string
to_currency: string
rate: string
fetched_at: date
financial_account
balance_cache is the live balance in minor units rows with deleted_at IS NOT NULL are soft-deleted; exclude them
id: string
user_id: string
name: string
type: string [enum: checking | savings | credit_card | cash | investment | crypto | loan | mortgage | property | vehicle | receivable | payable]
currency: string
institution: string (nullable)
icon: string (nullable)
is_liability: boolean
initial_balance: number [minor units — divide by 100]
balance_cache: number [minor units — divide by 100]
credit_limit: number (nullable) [minor units — divide by 100]
is_active: boolean
is_virtual: boolean
counterparty_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
net_worth_snapshot
id: string
user_id: string
total_assets: number [minor units — divide by 100]
total_liabilities: number [minor units — divide by 100]
net_worth: number [minor units — divide by 100]
currency: string
breakdown_json: json (nullable)
snapshot_date: string
created_at: date
price
cached market price per unit of the asset, keyed by (type, symbol); join asset on both columns use the latest row per (type, symbol) (order by fetched_at DESC, limit 1) for commodities the price is per troy ounce regardless of asset.unit; convert before comparing to per-unit purchase prices
id: string
type: string [enum: crypto | stock | real_estate | vehicle | commodity | bond | other]
symbol: string
price: number [minor units — divide by 100]
currency: string
fetched_at: date
transaction
amount is always non-negative; direction lives in type for spend/income totals, prefer the v_chat_transaction view rows with deleted_at IS NOT NULL are soft-deleted; exclude them
id: string
user_id: string
account_id: string (nullable)
type: string [enum: income | expense | transfer | buy | sell]
amount: number [minor units — divide by 100]
currency: string
exchange_rate: string (nullable)
date: string
description: string (nullable)
category_id: string (nullable)
envelope_id: string (nullable)
notes: string (nullable)
receipt_image_url: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
valuation
one row per asset per day; latest row is the current value
id: string
asset_id: string
value: number [minor units — divide by 100]
currency: string
fetched_at: date
v_chat_transaction (view)
Presentation view over transaction. Soft-deleted rows already excluded. Prefer this over the raw transaction table for any spend/income question.
id: string
user_id: string
date: date
type: string [enum: income | expense | transfer | buy | sell]
description: string (nullable)
currency: string
amount_major: numeric (always >= 0; major units, e.g. dollars not cents)
signed_amount: numeric (negative for expense and buy; positive for income and sell; zero for transfer and for cashless asset-conversion legs)
direction: string [enum: outflow | inflow | neutral]
account_id: string (nullable; null on the cashless legs of an asset conversion, which move no cash)
category_id: string (nullable)
category_name: string (nullable, joined from category)
envelope_id: string (nullable; direct budget assignment, often null)
envelope_name: string (nullable; the user's budget label, resolved via direct envelope_id OR via the envelope_category junction on category_id — match this when the user names a budget like "Dating")
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single Postgres SELECT statement. | |
| rationale | Yes | One-line plain-English explanation of why you're running this query. |