Files
sure/db/schema.rb
T
buzzromainandClaude Opus 5 07524d0d84 fix(holdings): a transfer must not set a cost basis (#3154)
* fix(holdings): a transfer must not set a cost basis

calculate_avg_cost sums every trade with a positive quantity, so an asset moved
in from elsewhere is counted as bought on the day it arrived. A coin acquired at
30k and transferred in at 60k reports a cost of 60k and no gain at all — a
number that looks authoritative and is wrong.

Nothing here can know what a transferred asset cost: the purchase happened
somewhere this app never saw. Leaving the cost unknown is what the method
already does when it has nothing to work from, and for the same stated reason
the fallback to market price was removed from it: "Previously this fell back to
current market price, which was misleading."

Two things it would be easy to get wrong, and both are covered:

- **One transfer makes the whole position unknown**, not just its own row.
  Averaging the purchases alone and applying that to every unit is the same
  fabrication in a quieter form: buy one at 30k, receive one, and the position
  reports 30k a unit for two units that did not cost that.
- **Unlabelled purchases are preserved.** `!=` is NULL for a row with no label,
  so a naive exclusion would drop the ordinary trades that carry none — which
  is most of them. Hence IS DISTINCT FROM.

Balances and value are unaffected: they come from holdings, which providers
import from the position itself rather than from trade history.

This reaches every integration that labels a movement as a transfer. Questrade
journals already did; the self-custody wallets do as of #3153.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(holdings): stop a stored figure outranking the transfer guard

Review on #3154, and the reviewers were right that the first pass only
covered half the path.

`Holding#avg_cost` returns a stored `cost_basis` before it ever calls
`calculate_avg_cost`, so the transfer guard was protecting only holdings
that had nothing stored. Worse, the stored value was itself wrong: both
calculators counted every positive-quantity trade toward the running
average, transfers included, and the materializer persisted that as a
`calculated` basis. A coin bought elsewhere at 30k and moved in at 60k
reported no gain at all, and said so with a figure that looks derived.

Fixed in the write path rather than the read one. Adding an `exists?` per
holding to `avg_cost` would have reintroduced exactly the N+1 the stored
value exists to avoid; clearing the stored value instead lets the read
path fall through to the guard that was already there.

Both calculators now exclude transfers from the average and mark the
security's basis unknown — the forward one for good, the reverse one from
the transfer's date onward, since the purchases before it still stand on
their own. `cost_basis_unknown` is carried separately from a nil
`cost_basis` because the materializer treats them differently: nil means
"nothing computed, leave what is there", unknown means "this cannot be
known, clear what is there".

A `manual` or `provider` basis survives. That is somebody asserting what
the position cost them, which is precisely the thing the app cannot derive
for a transfer.

The migration clears figures already stored. Positions heal on the next
materialization anyway, but a manual or disconnected account may not
materialize again for a long time, and the wrong number is not visibly
wrong.

The regression test materializes first and relabels after, because that is
the case that matters: a position already carrying a figure worked out
before anyone knew the movement was a transfer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

* fix(holdings): clear a transferred basis that recorded no source

Follow-up on the same review. `load_existing_holdings_map` loaded holdings
that were locked, sourced, or provider-owned — so a row carrying a
`cost_basis` with no `cost_basis_source` was invisible to it. The clearing
then saw no existing holding and left the figure standing, which meant the
rows least able to justify the number they hold were the ones that kept it.

The migration takes `[7.2]` to match `schema.rb` and the other 398
migrations, rather than the `[8.0]` I had written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

* fix(holdings): renumber the migration off a colliding version

`20260825120000` is already taken by `add_consumed_amount_to_goals` on the
goals stack. Two migrations sharing a version is not a merge conflict —
`schema_migrations` is keyed by it, so whichever landed second would be
recorded as already run and skipped in silence. For a data migration that
means transferred positions quietly keeping the cost basis this branch
exists to clear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

* docs(holdings): say why the basis guard keys on one label, not the set

#3192 landed Trade::INTERNAL_MOVEMENT_LABELS in this file, next to the
TRANSFER_LABEL this branch adds. Both rest on ownership being preserved, so
two constants sitting together invite the question of why the basis guard does
not simply use the broader one.

It could, and that would be a behaviour change: the sweep labels would start
clearing a cost basis too. Nothing has shown a sweep landing on a security, and
widening a guard that erases figures on the strength of a guess is the wrong
direction, so it stays narrow and now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 07:53:33 +02:00

2570 lines
126 KiB
Ruby
Generated

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_08_26_090000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "pgcrypto"
# Custom types defined in this database.
# Note that some types may not work with other database engines. Be careful if changing database.
create_enum "account_status", ["ok", "syncing", "error"]
create_enum "goal_pledge_kind", ["transfer", "manual_save"]
create_enum "goal_pledge_status", ["open", "matched", "cancelled", "expired"]
create_table "account_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.datetime "created_at", null: false
t.uuid "provider_id", null: false
t.string "provider_type", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "provider_type"], name: "index_account_providers_on_account_and_provider_type", unique: true
t.index ["provider_type", "provider_id"], name: "index_account_providers_on_provider_type_and_provider_id", unique: true
end
create_table "account_shares", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.datetime "created_at", null: false
t.boolean "include_in_finances", default: true, null: false
t.string "permission", default: "read_only", null: false
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["account_id", "user_id"], name: "index_account_shares_on_account_id_and_user_id", unique: true
t.index ["account_id"], name: "index_account_shares_on_account_id"
t.index ["user_id", "include_in_finances"], name: "index_account_shares_on_user_id_and_include_in_finances"
t.index ["user_id"], name: "index_account_shares_on_user_id"
t.check_constraint "permission::text = ANY (ARRAY['full_control'::character varying::text, 'read_write'::character varying::text, 'read_only'::character varying::text])", name: "chk_account_shares_permission"
end
create_table "account_statements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id"
t.string "account_last4_hint", limit: 4
t.string "account_name_hint", limit: 200
t.bigint "byte_size", null: false
t.string "checksum", limit: 64, null: false
t.decimal "closing_balance", precision: 19, scale: 4
t.string "content_sha256"
t.string "content_type", limit: 100, null: false
t.datetime "created_at", null: false
t.string "currency", limit: 3
t.uuid "family_id", null: false
t.string "filename", limit: 255, null: false
t.string "institution_name_hint", limit: 200
t.decimal "match_confidence", precision: 5, scale: 4
t.decimal "opening_balance", precision: 19, scale: 4
t.decimal "parser_confidence", precision: 5, scale: 4
t.date "period_end_on"
t.date "period_start_on"
t.string "review_status", default: "unmatched", null: false
t.jsonb "sanitized_parser_output", default: {}, null: false
t.string "source", default: "manual_upload", null: false
t.uuid "suggested_account_id"
t.datetime "updated_at", null: false
t.string "upload_status", default: "stored", null: false
t.index ["account_id", "period_start_on", "period_end_on"], name: "index_account_statements_on_account_period"
t.index ["account_id"], name: "index_account_statements_on_account_id"
t.index ["family_id", "checksum"], name: "index_account_statements_on_family_checksum"
t.index ["family_id", "content_sha256"], name: "index_account_statements_on_family_content_sha256", unique: true, where: "(content_sha256 IS NOT NULL)"
t.index ["family_id", "review_status"], name: "index_account_statements_on_family_review_status"
t.index ["family_id"], name: "index_account_statements_on_family_id"
t.index ["suggested_account_id", "review_status"], name: "index_account_statements_on_suggested_account_review"
t.index ["suggested_account_id"], name: "index_account_statements_on_suggested_account_id"
t.check_constraint "account_last4_hint IS NULL OR char_length(account_last4_hint::text) <= 4", name: "chk_account_statements_account_last4_hint_length"
t.check_constraint "account_name_hint IS NULL OR char_length(account_name_hint::text) <= 200", name: "chk_account_statements_account_name_hint_length"
t.check_constraint "byte_size <= 26214400", name: "chk_account_statements_byte_size_max"
t.check_constraint "byte_size > 0", name: "chk_account_statements_byte_size_positive"
t.check_constraint "char_length(checksum::text) <= 64", name: "chk_account_statements_checksum_length"
t.check_constraint "char_length(content_type::text) <= 100", name: "chk_account_statements_content_type_length"
t.check_constraint "char_length(filename::text) <= 255", name: "chk_account_statements_filename_length"
t.check_constraint "content_sha256 IS NULL OR content_sha256::text ~ '^[0-9a-f]{64}$'::text", name: "chk_account_statements_content_sha256"
t.check_constraint "currency IS NULL OR char_length(currency::text) <= 3", name: "chk_account_statements_currency_length"
t.check_constraint "institution_name_hint IS NULL OR char_length(institution_name_hint::text) <= 200", name: "chk_account_statements_institution_hint_length"
t.check_constraint "match_confidence IS NULL OR match_confidence >= 0::numeric AND match_confidence <= 1::numeric", name: "chk_account_statements_match_confidence"
t.check_constraint "parser_confidence IS NULL OR parser_confidence >= 0::numeric AND parser_confidence <= 1::numeric", name: "chk_account_statements_parser_confidence"
t.check_constraint "period_start_on IS NULL OR period_end_on IS NULL OR period_start_on <= period_end_on", name: "chk_account_statements_period_order"
t.check_constraint "review_status::text = ANY (ARRAY['unmatched'::character varying::text, 'linked'::character varying::text, 'rejected'::character varying::text])", name: "chk_account_statements_review_status"
t.check_constraint "source::text = 'manual_upload'::text", name: "chk_account_statements_source"
t.check_constraint "upload_status::text = ANY (ARRAY['stored'::character varying::text, 'failed'::character varying::text])", name: "chk_account_statements_upload_status"
end
create_table "accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.integer "account_providers_count", default: 0, null: false
t.uuid "accountable_id"
t.string "accountable_type"
t.decimal "balance", precision: 19, scale: 4
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Loan'::character varying)::text, ('CreditCard'::character varying)::text, ('OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
t.datetime "created_at", null: false
t.string "currency"
t.datetime "disabled_at"
t.boolean "enable_category_matcher", default: true, null: false
t.boolean "exclude_from_reports", default: false, null: false
t.uuid "family_id", null: false
t.uuid "import_id"
t.string "institution_domain"
t.string "institution_name"
t.jsonb "locked_attributes", default: {}
t.string "name"
t.text "notes"
t.uuid "owner_id"
t.uuid "plaid_account_id"
t.uuid "simplefin_account_id"
t.string "status", default: "active"
t.string "subtype"
t.datetime "updated_at", null: false
t.index ["accountable_id", "accountable_type"], name: "index_accounts_on_accountable_id_and_accountable_type"
t.index ["accountable_type"], name: "index_accounts_on_accountable_type"
t.index ["currency"], name: "index_accounts_on_currency"
t.index ["family_id", "accountable_type"], name: "index_accounts_on_family_id_and_accountable_type"
t.index ["family_id", "exclude_from_reports"], name: "index_accounts_on_family_id_and_exclude_from_reports"
t.index ["family_id", "id"], name: "index_accounts_on_family_id_and_id"
t.index ["family_id", "status", "accountable_type"], name: "index_accounts_on_family_id_status_accountable_type"
t.index ["family_id", "status"], name: "index_accounts_on_family_id_and_status"
t.index ["family_id"], name: "index_accounts_on_family_id"
t.index ["import_id"], name: "index_accounts_on_import_id"
t.index ["owner_id"], name: "index_accounts_on_owner_id"
t.index ["plaid_account_id"], name: "index_accounts_on_plaid_account_id"
t.index ["simplefin_account_id"], name: "index_accounts_on_simplefin_account_id"
t.index ["status"], name: "index_accounts_on_status"
end
create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "blob_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.uuid "record_id", null: false
t.string "record_type", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.bigint "byte_size", null: false
t.string "checksum"
t.string "content_type"
t.datetime "created_at", null: false
t.string "filename", null: false
t.string "key", null: false
t.text "metadata"
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "addresses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "addressable_id"
t.string "addressable_type"
t.string "country"
t.string "county"
t.datetime "created_at", null: false
t.string "line1"
t.string "line2"
t.string "locality"
t.string "postal_code"
t.string "region"
t.datetime "updated_at", null: false
t.index ["addressable_type", "addressable_id"], name: "index_addresses_on_addressable"
end
create_table "akahu_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.uuid "akahu_item_id", null: false
t.decimal "available_balance", precision: 19, scale: 4
t.decimal "balance_limit", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.string "formatted_account"
t.jsonb "institution_metadata"
t.string "name"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_akahu_accounts_on_account_id"
t.index ["akahu_item_id", "account_id"], name: "index_akahu_accounts_on_item_and_account_id", unique: true, where: "(account_id IS NOT NULL)"
t.index ["akahu_item_id"], name: "index_akahu_accounts_on_akahu_item_id"
end
create_table "akahu_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "app_token"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.text "user_token"
t.index ["family_id"], name: "index_akahu_items_on_family_id"
t.index ["status"], name: "index_akahu_items_on_status"
end
create_table "api_keys", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "display_key", null: false
t.datetime "expires_at"
t.datetime "last_used_at"
t.string "name"
t.datetime "revoked_at"
t.json "scopes"
t.string "source", default: "web"
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["display_key"], name: "index_api_keys_on_display_key", unique: true
t.index ["revoked_at"], name: "index_api_keys_on_revoked_at"
t.index ["user_id", "source"], name: "index_api_keys_on_user_id_and_source"
t.index ["user_id"], name: "index_api_keys_on_user_id"
end
create_table "archived_exports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "download_token_digest", null: false
t.string "email", null: false
t.datetime "expires_at", null: false
t.string "family_name"
t.datetime "updated_at", null: false
t.index ["download_token_digest"], name: "index_archived_exports_on_download_token_digest", unique: true
t.index ["expires_at"], name: "index_archived_exports_on_expires_at"
end
create_table "balances", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.decimal "balance", precision: 19, scale: 4, null: false
t.decimal "cash_adjustments", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
t.decimal "cash_inflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "cash_outflows", precision: 19, scale: 4, default: "0.0", null: false
t.datetime "created_at", null: false
t.string "currency", default: "USD", null: false
t.date "date", null: false
t.virtual "end_balance", type: :decimal, precision: 19, scale: 4, as: "(((start_cash_balance + ((cash_inflows - cash_outflows) * (flows_factor)::numeric)) + cash_adjustments) + (((start_non_cash_balance + ((non_cash_inflows - non_cash_outflows) * (flows_factor)::numeric)) + net_market_flows) + non_cash_adjustments))", stored: true
t.virtual "end_cash_balance", type: :decimal, precision: 19, scale: 4, as: "((start_cash_balance + ((cash_inflows - cash_outflows) * (flows_factor)::numeric)) + cash_adjustments)", stored: true
t.virtual "end_non_cash_balance", type: :decimal, precision: 19, scale: 4, as: "(((start_non_cash_balance + ((non_cash_inflows - non_cash_outflows) * (flows_factor)::numeric)) + net_market_flows) + non_cash_adjustments)", stored: true
t.integer "flows_factor", default: 1, null: false
t.decimal "net_market_flows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_adjustments", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_inflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_outflows", precision: 19, scale: 4, default: "0.0", null: false
t.virtual "start_balance", type: :decimal, precision: 19, scale: 4, as: "(start_cash_balance + start_non_cash_balance)", stored: true
t.decimal "start_cash_balance", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "start_non_cash_balance", precision: 19, scale: 4, default: "0.0", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "date", "currency"], name: "index_account_balances_on_account_id_date_currency_unique", unique: true
t.index ["account_id", "date"], name: "index_balances_on_account_id_and_date", order: { date: :desc }
t.index ["account_id"], name: "index_balances_on_account_id"
end
create_table "binance_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_type"
t.uuid "binance_item_id", null: false
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "extra", default: {}, null: false
t.jsonb "institution_metadata"
t.string "name"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["account_type"], name: "index_binance_accounts_on_account_type"
t.index ["binance_item_id", "account_type"], name: "index_binance_accounts_on_item_and_type", unique: true
t.index ["binance_item_id"], name: "index_binance_accounts_on_binance_item_id"
end
create_table "binance_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_key"
t.text "api_secret"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_binance_items_on_family_id"
t.index ["status"], name: "index_binance_items_on_status"
end
create_table "brex_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id", null: false
t.string "account_kind", default: "cash", null: false
t.decimal "account_limit", precision: 19, scale: 4
t.string "account_status"
t.string "account_type"
t.decimal "available_balance", precision: 19, scale: 4
t.uuid "brex_item_id", null: false
t.datetime "created_at", null: false
t.string "currency", default: "USD", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.string "name"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["brex_item_id", "account_id"], name: "index_brex_accounts_on_item_and_account_id", unique: true
t.index ["brex_item_id"], name: "index_brex_accounts_on_brex_item_id"
end
create_table "brex_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "base_url"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name", null: false
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.text "token", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_brex_items_on_family_id"
t.index ["status"], name: "index_brex_items_on_status"
end
create_table "budget_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "budget_id", null: false
t.decimal "budgeted_spending", precision: 19, scale: 4, null: false
t.uuid "category_id", null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "rolled_over_amount", precision: 19, scale: 4, default: "0.0", null: false
t.boolean "rollover_enabled", default: false, null: false
t.datetime "updated_at", null: false
t.index ["budget_id", "category_id"], name: "index_budget_categories_on_budget_id_and_category_id", unique: true
t.index ["budget_id"], name: "index_budget_categories_on_budget_id"
t.index ["category_id"], name: "index_budget_categories_on_category_id"
t.check_constraint "rolled_over_amount >= 0::numeric", name: "chk_budget_categories_rolled_over_amount_non_negative"
end
create_table "budget_shares", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "owner_id", null: false
t.string "permission", default: "read_only", null: false
t.datetime "updated_at", null: false
t.uuid "viewer_id", null: false
t.index ["owner_id", "viewer_id"], name: "index_budget_shares_on_owner_id_and_viewer_id", unique: true
t.index ["owner_id"], name: "index_budget_shares_on_owner_id"
t.index ["viewer_id"], name: "index_budget_shares_on_viewer_id"
end
create_table "budgets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "budgeted_spending", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency", null: false
t.date "end_date", null: false
t.decimal "expected_income", precision: 19, scale: 4
t.uuid "family_id", null: false
t.date "start_date", null: false
t.datetime "updated_at", null: false
t.uuid "user_id"
t.index ["family_id", "start_date", "end_date", "user_id"], name: "index_budgets_personal_unique", unique: true, where: "(user_id IS NOT NULL)"
t.index ["family_id", "start_date", "end_date"], name: "index_budgets_shared_unique", unique: true, where: "(user_id IS NULL)"
t.index ["family_id"], name: "index_budgets_on_family_id"
t.index ["user_id"], name: "index_budgets_on_user_id"
end
create_table "categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "classification_unused", default: "expense", null: false
t.string "color", default: "#6172F3", null: false
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.datetime "last_used_at"
t.string "lucide_icon", default: "shapes", null: false
t.string "name", null: false
t.uuid "parent_id"
t.datetime "updated_at", null: false
t.index ["family_id", "last_used_at"], name: "index_categories_on_family_id_and_last_used_at"
t.index ["family_id", "name"], name: "index_categories_on_family_id_and_name", unique: true
t.index ["family_id"], name: "index_categories_on_family_id"
end
create_table "chats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "error"
t.string "instructions"
t.string "latest_assistant_response_id"
t.string "title", null: false
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["user_id"], name: "index_chats_on_user_id"
end
create_table "coinbase_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.uuid "coinbase_item_id", null: false
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.string "name"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_coinbase_accounts_on_account_id"
t.index ["coinbase_item_id", "account_id"], name: "index_coinbase_accounts_on_item_and_account_id", unique: true, where: "(account_id IS NOT NULL)"
t.index ["coinbase_item_id"], name: "index_coinbase_accounts_on_coinbase_item_id"
end
create_table "coinbase_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_key"
t.text "api_secret"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_coinbase_items_on_family_id"
t.index ["status"], name: "index_coinbase_items_on_status"
end
create_table "coinstats_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.uuid "coinstats_item_id", null: false
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.string "name"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.string "wallet_address"
t.index ["coinstats_item_id", "account_id", "wallet_address"], name: "index_coinstats_accounts_on_item_account_and_wallet", unique: true
t.index ["coinstats_item_id"], name: "index_coinstats_accounts_on_coinstats_item_id"
end
create_table "coinstats_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "api_key", null: false
t.datetime "created_at", null: false
t.string "exchange_connection_id"
t.string "exchange_portfolio_id"
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["exchange_connection_id"], name: "index_coinstats_items_on_exchange_connection_id"
t.index ["family_id", "exchange_portfolio_id"], name: "index_coinstats_items_on_family_id_and_exchange_portfolio_id", unique: true, where: "(exchange_portfolio_id IS NOT NULL)"
t.index ["family_id"], name: "index_coinstats_items_on_family_id"
t.index ["status"], name: "index_coinstats_items_on_status"
end
create_table "credit_cards", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "annual_fee", precision: 10, scale: 2
t.decimal "apr", precision: 10, scale: 2
t.decimal "available_credit", precision: 10, scale: 2
t.datetime "created_at", null: false
t.date "expiration_date"
t.jsonb "locked_attributes", default: {}
t.decimal "minimum_payment", precision: 10, scale: 2
t.string "subtype"
t.datetime "updated_at", null: false
end
create_table "cryptos", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.string "tax_treatment", default: "taxable", null: false
t.datetime "updated_at", null: false
end
create_table "data_enrichments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "attribute_name"
t.datetime "created_at", null: false
t.uuid "enrichable_id", null: false
t.string "enrichable_type", null: false
t.jsonb "metadata"
t.string "source"
t.datetime "updated_at", null: false
t.jsonb "value"
t.index ["enrichable_id", "enrichable_type", "source", "attribute_name"], name: "idx_on_enrichable_id_enrichable_type_source_attribu_5be5f63e08", unique: true
t.index ["enrichable_type", "enrichable_id"], name: "index_data_enrichments_on_enrichable"
end
create_table "debug_log_entries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id"
t.uuid "account_provider_id"
t.string "category", null: false
t.datetime "created_at", null: false
t.uuid "family_id"
t.string "level", null: false
t.text "message", null: false
t.jsonb "metadata", default: {}, null: false
t.string "provider_key"
t.string "source", null: false
t.datetime "updated_at", null: false
t.uuid "user_id"
t.index ["account_id"], name: "index_debug_log_entries_on_account_id"
t.index ["account_provider_id"], name: "index_debug_log_entries_on_account_provider_id"
t.index ["category", "created_at"], name: "index_debug_log_entries_on_category_and_created_at"
t.index ["category"], name: "index_debug_log_entries_on_category"
t.index ["created_at"], name: "index_debug_log_entries_on_created_at"
t.index ["family_id"], name: "index_debug_log_entries_on_family_id"
t.index ["level"], name: "index_debug_log_entries_on_level"
t.index ["provider_key", "created_at"], name: "index_debug_log_entries_on_provider_key_and_created_at"
t.index ["provider_key"], name: "index_debug_log_entries_on_provider_key"
t.index ["source"], name: "index_debug_log_entries_on_source"
t.index ["user_id"], name: "index_debug_log_entries_on_user_id"
t.check_constraint "level::text = ANY (ARRAY['debug'::character varying::text, 'info'::character varying::text, 'warn'::character varying::text, 'error'::character varying::text])", name: "chk_debug_log_entries_level"
end
create_table "depositories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.datetime "updated_at", null: false
end
create_table "enable_banking_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.datetime "created_at", null: false
t.decimal "credit_limit", precision: 19, scale: 4
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.uuid "enable_banking_item_id", null: false
t.string "iban"
t.jsonb "identification_hashes", default: []
t.jsonb "institution_metadata"
t.string "name"
t.string "product"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.boolean "treat_balance_as_available_credit", default: false, null: false
t.string "uid"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_enable_banking_accounts_on_account_id"
t.index ["enable_banking_item_id"], name: "index_enable_banking_accounts_on_enable_banking_item_id"
t.index ["identification_hashes"], name: "index_enable_banking_accounts_on_identification_hashes", using: :gin
end
create_table "enable_banking_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "application_id"
t.string "aspsp_auth_approach"
t.string "aspsp_id"
t.integer "aspsp_maximum_consent_validity"
t.string "aspsp_name"
t.jsonb "aspsp_psu_types", default: []
t.jsonb "aspsp_required_psu_headers", default: []
t.string "authorization_id"
t.text "client_certificate"
t.string "country_code"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "last_psu_ip"
t.string "name"
t.boolean "pending_account_setup", default: false
t.string "psu_type"
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.datetime "session_expires_at"
t.string "session_id"
t.string "status", default: "good"
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_enable_banking_items_on_family_id"
t.index ["status"], name: "index_enable_banking_items_on_status"
end
create_table "entries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.decimal "amount", precision: 19, scale: 4, null: false
t.datetime "created_at", null: false
t.string "currency"
t.date "date"
t.uuid "entryable_id"
t.string "entryable_type"
t.boolean "excluded", default: false
t.string "external_id"
t.uuid "import_id"
t.boolean "import_locked", default: false, null: false
t.jsonb "locked_attributes", default: {}
t.string "name", null: false
t.text "notes"
t.uuid "parent_entry_id"
t.string "plaid_id"
t.datetime "reconciled_at"
t.uuid "reconciled_by_statement_id"
t.string "source"
t.datetime "updated_at", null: false
t.boolean "user_modified", default: false, null: false
t.index "lower((name)::text)", name: "index_entries_on_lower_name"
t.index ["account_id", "date", "entryable_id"], name: "index_entries_on_investment_totals_lookup", where: "(((entryable_type)::text = 'Trade'::text) AND (excluded = false))"
t.index ["account_id", "date"], name: "index_entries_on_account_id_and_date"
t.index ["account_id", "reconciled_at"], name: "index_entries_on_account_and_reconciled_at", where: "(reconciled_at IS NOT NULL)"
t.index ["account_id", "source", "external_id"], name: "index_entries_on_account_source_and_external_id", unique: true, where: "((external_id IS NOT NULL) AND (source IS NOT NULL))"
t.index ["account_id"], name: "index_entries_on_account_id"
t.index ["currency", "amount", "date", "account_id"], name: "index_entries_on_transfer_match_lookup", where: "(((entryable_type)::text = 'Transaction'::text) AND (excluded = false))"
t.index ["date"], name: "index_entries_on_date"
t.index ["entryable_type"], name: "index_entries_on_entryable_type"
t.index ["import_id"], name: "index_entries_on_import_id"
t.index ["import_locked"], name: "index_entries_on_import_locked_true", where: "(import_locked = true)"
t.index ["parent_entry_id"], name: "index_entries_on_parent_entry_id"
t.index ["reconciled_by_statement_id"], name: "index_entries_on_reconciled_by_statement", where: "(reconciled_by_statement_id IS NOT NULL)"
t.index ["user_modified"], name: "index_entries_on_user_modified_true", where: "(user_modified = true)"
t.check_constraint "reconciled_by_statement_id IS NULL OR reconciled_at IS NOT NULL", name: "chk_entries_reconciled_at_present_when_statement_set"
end
create_table "eval_datasets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.boolean "active", default: true
t.datetime "created_at", null: false
t.string "description"
t.string "eval_type", null: false
t.jsonb "metadata", default: {}
t.string "name", null: false
t.integer "sample_count", default: 0
t.datetime "updated_at", null: false
t.string "version", default: "1.0", null: false
t.index ["eval_type", "active"], name: "index_eval_datasets_on_eval_type_and_active"
t.index ["name"], name: "index_eval_datasets_on_name", unique: true
end
create_table "eval_results", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.jsonb "actual_output", null: false
t.boolean "alternative_match", default: false
t.integer "completion_tokens"
t.boolean "correct", null: false
t.decimal "cost", precision: 10, scale: 6
t.datetime "created_at", null: false
t.uuid "eval_run_id", null: false
t.uuid "eval_sample_id", null: false
t.boolean "exact_match", default: false
t.float "fuzzy_score"
t.boolean "hierarchical_match", default: false
t.integer "latency_ms"
t.jsonb "metadata", default: {}
t.boolean "null_expected", default: false
t.boolean "null_returned", default: false
t.integer "prompt_tokens"
t.datetime "updated_at", null: false
t.index ["eval_run_id", "correct"], name: "index_eval_results_on_eval_run_id_and_correct"
t.index ["eval_run_id"], name: "index_eval_results_on_eval_run_id"
t.index ["eval_sample_id"], name: "index_eval_results_on_eval_sample_id"
end
create_table "eval_runs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "completed_at"
t.datetime "created_at", null: false
t.text "error_message"
t.uuid "eval_dataset_id", null: false
t.jsonb "metrics", default: {}
t.string "model", null: false
t.string "name"
t.string "provider", null: false
t.jsonb "provider_config", default: {}
t.datetime "started_at"
t.string "status", default: "pending", null: false
t.integer "total_completion_tokens", default: 0
t.decimal "total_cost", precision: 10, scale: 6, default: "0.0"
t.integer "total_prompt_tokens", default: 0
t.datetime "updated_at", null: false
t.index ["eval_dataset_id", "model"], name: "index_eval_runs_on_eval_dataset_id_and_model"
t.index ["eval_dataset_id"], name: "index_eval_runs_on_eval_dataset_id"
t.index ["provider", "model"], name: "index_eval_runs_on_provider_and_model"
t.index ["status"], name: "index_eval_runs_on_status"
end
create_table "eval_samples", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.jsonb "context_data", default: {}
t.datetime "created_at", null: false
t.string "difficulty", default: "medium"
t.uuid "eval_dataset_id", null: false
t.jsonb "expected_output", null: false
t.jsonb "input_data", null: false
t.jsonb "metadata", default: {}
t.string "tags", default: [], array: true
t.datetime "updated_at", null: false
t.index ["eval_dataset_id", "difficulty"], name: "index_eval_samples_on_eval_dataset_id_and_difficulty"
t.index ["eval_dataset_id"], name: "index_eval_samples_on_eval_dataset_id"
t.index ["tags"], name: "index_eval_samples_on_tags", using: :gin
end
create_table "exchange_rate_pairs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.date "first_provider_rate_on"
t.string "from_currency", null: false
t.string "provider_name"
t.string "to_currency", null: false
t.datetime "updated_at", null: false
t.index ["from_currency", "to_currency"], name: "index_exchange_rate_pairs_on_pair_unique", unique: true
end
create_table "exchange_rates", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.date "date", null: false
t.string "from_currency", null: false
t.decimal "rate", null: false
t.string "to_currency", null: false
t.datetime "updated_at", null: false
t.index ["from_currency", "to_currency", "date"], name: "index_exchange_rates_on_base_converted_date_unique", unique: true
t.index ["from_currency"], name: "index_exchange_rates_on_from_currency"
t.index ["to_currency"], name: "index_exchange_rates_on_to_currency"
end
create_table "families", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "assistant_type", default: "builtin", null: false
t.boolean "auto_sync_on_login", default: true, null: false
t.string "country", default: "US"
t.datetime "created_at", null: false
t.string "currency", default: "USD"
t.boolean "data_enrichment_enabled", default: false
t.string "date_format", default: "%m-%d-%Y"
t.string "default_account_sharing", default: "shared", null: false
t.boolean "early_access", default: false
t.string "enabled_currencies", array: true
t.boolean "household_budget_enabled", default: true, null: false
t.datetime "last_sync_all_attempted_at"
t.datetime "latest_sync_activity_at", default: -> { "CURRENT_TIMESTAMP" }
t.datetime "latest_sync_completed_at", default: -> { "CURRENT_TIMESTAMP" }
t.string "locale", default: "en"
t.string "moniker", default: "Family", null: false
t.integer "month_start_day", default: 1, null: false
t.string "name"
t.boolean "personal_budgets", default: false, null: false
t.boolean "recurring_transactions_disabled", default: false, null: false
t.string "stripe_customer_id"
t.string "timezone"
t.datetime "updated_at", null: false
t.string "vector_store_id"
t.check_constraint "default_account_sharing::text = ANY (ARRAY['shared'::character varying::text, 'private'::character varying::text])", name: "chk_families_default_account_sharing"
t.check_constraint "month_start_day >= 1 AND month_start_day <= 28", name: "month_start_day_range"
end
create_table "family_documents", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "content_type"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.integer "file_size"
t.string "filename", null: false
t.jsonb "metadata", default: {}
t.string "provider_file_id"
t.string "status", default: "pending", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_family_documents_on_family_id"
t.index ["provider_file_id"], name: "index_family_documents_on_provider_file_id"
t.index ["status"], name: "index_family_documents_on_status"
end
create_table "family_exports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "status", default: "pending", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_family_exports_on_family_id"
end
create_table "family_merchant_associations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.uuid "merchant_id", null: false
t.datetime "unlinked_at"
t.datetime "updated_at", null: false
t.index ["family_id", "merchant_id"], name: "idx_on_family_id_merchant_id_23e883e08f", unique: true
t.index ["family_id"], name: "index_family_merchant_associations_on_family_id"
t.index ["merchant_id"], name: "index_family_merchant_associations_on_merchant_id"
end
create_table "goal_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.decimal "allocated_amount", precision: 19, scale: 4
t.datetime "created_at", null: false
t.uuid "goal_id", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_goal_accounts_on_account_id"
t.index ["goal_id", "account_id"], name: "index_savings_goal_accounts_on_goal_and_account", unique: true
t.index ["goal_id"], name: "index_goal_accounts_on_goal_id"
t.check_constraint "allocated_amount IS NULL OR allocated_amount >= 0::numeric", name: "chk_goal_accounts_allocation_non_negative"
end
create_table "goal_pledges", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.decimal "amount", precision: 19, scale: 4, null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.datetime "expires_at", null: false
t.uuid "goal_id", null: false
t.enum "kind", null: false, enum_type: "goal_pledge_kind"
t.uuid "matched_transaction_id"
t.enum "status", default: "open", null: false, enum_type: "goal_pledge_status"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_goal_pledges_on_account_id"
t.index ["goal_id", "status"], name: "index_goal_pledges_on_goal_id_and_status"
t.index ["goal_id"], name: "index_goal_pledges_on_goal_id"
t.index ["matched_transaction_id"], name: "index_goal_pledges_on_matched_transaction_id", unique: true, where: "(matched_transaction_id IS NOT NULL)"
t.index ["status", "expires_at"], name: "index_goal_pledges_open_by_expiry", where: "(status = 'open'::goal_pledge_status)"
t.check_constraint "amount > 0::numeric", name: "chk_goal_pledges_amount_positive"
end
create_table "goals", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "color"
t.decimal "completed_amount", precision: 19, scale: 4
t.datetime "completed_at"
t.decimal "consumed_amount", precision: 19, scale: 4, default: "0.0", null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.uuid "family_id", null: false
t.string "icon"
t.string "kind", default: "one_off", null: false
t.string "name", null: false
t.text "notes"
t.string "progress_basis", default: "balance", null: false
t.string "state", default: "active", null: false
t.decimal "target_amount", precision: 19, scale: 4, null: false
t.date "target_date"
t.string "target_mode", default: "fixed", null: false
t.integer "target_months"
t.datetime "updated_at", null: false
t.index ["family_id", "state"], name: "index_goals_on_family_id_and_state"
t.index ["family_id"], name: "index_goals_on_family_id"
t.check_constraint "char_length(name::text) <= 255", name: "chk_savings_goals_name_length"
t.check_constraint "consumed_amount >= 0::numeric", name: "chk_goals_consumed_amount_non_negative"
t.check_constraint "kind::text = ANY (ARRAY['one_off'::character varying::text, 'maintained'::character varying::text])", name: "chk_goals_kind_enum"
t.check_constraint "progress_basis::text = ANY (ARRAY['balance'::character varying::text, 'contributions'::character varying::text])", name: "chk_goals_progress_basis_enum"
t.check_constraint "state::text = ANY (ARRAY['active'::character varying::text, 'paused'::character varying::text, 'completed'::character varying::text, 'archived'::character varying::text])", name: "chk_savings_goals_state_enum"
t.check_constraint "target_amount > 0::numeric", name: "chk_savings_goals_target_amount_positive"
t.check_constraint "target_mode::text = ANY (ARRAY['fixed'::character varying::text, 'months_of_expenses'::character varying::text])", name: "chk_goals_target_mode_enum"
end
create_table "holdings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
t.uuid "account_provider_id"
t.decimal "amount", precision: 19, scale: 4, null: false
t.decimal "cost_basis", precision: 19, scale: 4
t.boolean "cost_basis_locked", default: false, null: false
t.string "cost_basis_source"
t.datetime "created_at", null: false
t.string "currency", null: false
t.date "date", null: false
t.string "external_id"
t.decimal "price", precision: 19, scale: 4, null: false
t.uuid "provider_security_id"
t.decimal "qty", precision: 24, scale: 8, null: false
t.uuid "security_id", null: false
t.boolean "security_locked", default: false, null: false
t.datetime "updated_at", null: false
t.index ["account_id", "external_id"], name: "idx_holdings_on_account_id_external_id_unique", unique: true, where: "(external_id IS NOT NULL)"
t.index ["account_id", "security_id", "date", "currency"], name: "idx_on_account_id_security_id_date_currency_5323e39f8b", unique: true
t.index ["account_id"], name: "index_holdings_on_account_id"
t.index ["account_provider_id"], name: "index_holdings_on_account_provider_id"
t.index ["provider_security_id"], name: "index_holdings_on_provider_security_id"
t.index ["security_id"], name: "index_holdings_on_security_id"
end
create_table "ibkr_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "cash_balance", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.string "ibkr_account_id"
t.uuid "ibkr_item_id", null: false
t.jsonb "institution_metadata"
t.datetime "last_activities_sync"
t.datetime "last_holdings_sync"
t.string "name"
t.jsonb "raw_activities_payload", default: {}
t.jsonb "raw_cash_report_payload", default: []
t.jsonb "raw_equity_summary_payload", default: [], null: false
t.jsonb "raw_holdings_payload", default: []
t.date "report_date"
t.datetime "updated_at", null: false
t.index ["ibkr_item_id", "ibkr_account_id"], name: "index_ibkr_accounts_on_item_and_ibkr_account_id", unique: true, where: "(ibkr_account_id IS NOT NULL)"
t.index ["ibkr_item_id"], name: "index_ibkr_accounts_on_ibkr_item_id"
end
create_table "ibkr_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.string "query_id"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.string "token"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_ibkr_items_on_family_id"
t.index ["status"], name: "index_ibkr_items_on_status"
end
create_table "impersonation_session_logs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "action"
t.string "controller"
t.datetime "created_at", null: false
t.uuid "impersonation_session_id", null: false
t.string "ip_address"
t.string "method"
t.text "path"
t.datetime "updated_at", null: false
t.text "user_agent"
t.index ["impersonation_session_id"], name: "index_impersonation_session_logs_on_impersonation_session_id"
end
create_table "impersonation_sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "impersonated_id", null: false
t.uuid "impersonator_id", null: false
t.string "status", default: "pending", null: false
t.datetime "updated_at", null: false
t.index ["impersonated_id"], name: "index_impersonation_sessions_on_impersonated_id"
t.index ["impersonator_id"], name: "index_impersonation_sessions_on_impersonator_id"
end
create_table "import_mappings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.boolean "create_when_empty", default: true
t.datetime "created_at", null: false
t.uuid "import_id", null: false
t.string "key"
t.uuid "mappable_id"
t.string "mappable_type"
t.string "type", null: false
t.datetime "updated_at", null: false
t.string "value"
t.index ["import_id"], name: "index_import_mappings_on_import_id"
t.index ["mappable_type", "mappable_id"], name: "index_import_mappings_on_mappable"
end
create_table "import_rows", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account"
t.text "actions"
t.boolean "active"
t.string "amount"
t.string "category"
t.string "category_classification"
t.string "category_color"
t.string "category_icon"
t.string "category_parent"
t.text "conditions"
t.datetime "created_at", null: false
t.string "currency"
t.string "date"
t.string "effective_date"
t.string "entity_type"
t.string "exchange_operating_mic"
t.uuid "import_id", null: false
t.string "merchant_color"
t.string "merchant_website"
t.string "name"
t.text "notes"
t.string "price"
t.string "qty"
t.string "resource_type"
t.integer "source_row_number", null: false
t.string "tags"
t.string "ticker"
t.datetime "updated_at", null: false
t.index ["import_id", "source_row_number"], name: "index_import_rows_on_import_id_and_source_row_number", unique: true
t.index ["import_id"], name: "index_import_rows_on_import_id"
t.check_constraint "source_row_number > 0", name: "chk_import_rows_source_row_number_positive"
end
create_table "import_sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "client_session_id", limit: 255
t.datetime "created_at", null: false
t.jsonb "error_details", default: {}, null: false
t.integer "expected_chunks"
t.uuid "family_id", null: false
t.string "import_type", default: "SureImport", null: false
t.string "status", default: "pending", null: false
t.jsonb "summary", default: {}, null: false
t.datetime "updated_at", null: false
t.index ["family_id", "client_session_id"], name: "idx_import_sessions_on_family_client_session", unique: true, where: "(client_session_id IS NOT NULL)"
t.index ["family_id", "status"], name: "index_import_sessions_on_family_id_and_status"
t.index ["family_id"], name: "index_import_sessions_on_family_id"
t.index ["id", "family_id"], name: "idx_import_sessions_on_id_family", unique: true
t.check_constraint "client_session_id IS NULL OR btrim(client_session_id::text) <> ''::text", name: "chk_import_sessions_client_session_id_present"
t.check_constraint "expected_chunks IS NULL OR expected_chunks > 0", name: "chk_import_sessions_expected_chunks_positive"
t.check_constraint "import_type::text = 'SureImport'::text", name: "chk_import_sessions_import_type"
t.check_constraint "jsonb_typeof(error_details) = 'object'::text", name: "chk_import_sessions_error_details_object"
t.check_constraint "jsonb_typeof(summary) = 'object'::text", name: "chk_import_sessions_summary_object"
t.check_constraint "status::text = ANY (ARRAY['pending'::character varying::text, 'importing'::character varying::text, 'complete'::character varying::text, 'failed'::character varying::text])", name: "chk_import_sessions_status"
end
create_table "import_source_mappings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.uuid "import_session_id", null: false
t.string "source_id", limit: 255, null: false
t.string "source_type", limit: 64, null: false
t.uuid "target_id", null: false
t.string "target_type", null: false
t.datetime "updated_at", null: false
t.index ["family_id", "source_type", "source_id"], name: "idx_import_source_mappings_on_family_source"
t.index ["family_id"], name: "index_import_source_mappings_on_family_id"
t.index ["import_session_id", "source_type", "source_id"], name: "index_import_source_mappings_on_session_type_and_source", unique: true
t.index ["import_session_id"], name: "index_import_source_mappings_on_import_session_id"
t.index ["target_type", "target_id"], name: "idx_import_source_mappings_on_target"
t.check_constraint "btrim(source_id::text) <> ''::text", name: "chk_import_source_mappings_source_id_present"
t.check_constraint "btrim(source_type::text) <> ''::text", name: "chk_import_source_mappings_source_type_present"
t.check_constraint "btrim(target_type::text) <> ''::text", name: "chk_import_source_mappings_target_type_present"
t.check_constraint "source_type::text = ANY (ARRAY['Account'::character varying::text, 'Category'::character varying::text, 'Tag'::character varying::text, 'Merchant'::character varying::text, 'RecurringTransaction'::character varying::text, 'Transaction'::character varying::text, 'Budget'::character varying::text, 'Security'::character varying::text, 'Rule'::character varying::text])", name: "chk_import_source_mappings_source_type"
t.check_constraint "target_type::text = ANY (ARRAY['Account'::character varying::text, 'Category'::character varying::text, 'Tag'::character varying::text, 'Merchant'::character varying::text, 'RecurringTransaction'::character varying::text, 'Transaction'::character varying::text, 'Budget'::character varying::text, 'Security'::character varying::text, 'Rule'::character varying::text])", name: "chk_import_source_mappings_target_type"
end
create_table "imports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_col_label"
t.uuid "account_id"
t.uuid "account_statement_id"
t.text "ai_summary"
t.string "amount_col_label"
t.string "amount_type_identifier_value"
t.string "amount_type_inflow_value"
t.string "amount_type_strategy", default: "signed_amount"
t.string "category_col_label"
t.string "checksum", limit: 64
t.string "client_chunk_id", limit: 255
t.string "col_sep", default: ","
t.jsonb "column_mappings"
t.datetime "created_at", null: false
t.string "currency_col_label"
t.string "date_col_label"
t.string "date_format", default: "%m/%d/%Y"
t.string "document_type"
t.string "entity_type_col_label"
t.string "error"
t.jsonb "error_details", default: {}, null: false
t.string "exchange_operating_mic_col_label"
t.jsonb "expected_record_counts", default: {}, null: false
t.jsonb "extracted_data"
t.uuid "family_id", null: false
t.uuid "import_session_id"
t.string "name_col_label"
t.string "normalized_csv_str"
t.string "notes_col_label"
t.string "number_format"
t.string "price_col_label"
t.string "qty_col_label"
t.string "raw_file_str"
t.jsonb "readback_verification", default: {}, null: false
t.integer "rows_count", default: 0, null: false
t.integer "rows_to_skip", default: 0, null: false
t.integer "sequence"
t.string "signage_convention", default: "inflows_positive"
t.string "status"
t.jsonb "summary", default: {}, null: false
t.string "tags_col_label"
t.string "ticker_col_label"
t.string "type", null: false
t.datetime "updated_at", null: false
t.index ["account_statement_id"], name: "index_imports_on_account_statement_id"
t.index ["family_id"], name: "index_imports_on_family_id"
t.index ["import_session_id", "client_chunk_id"], name: "idx_imports_on_session_client_chunk", unique: true, where: "((import_session_id IS NOT NULL) AND (client_chunk_id IS NOT NULL))"
t.index ["import_session_id", "sequence"], name: "idx_imports_on_session_sequence", unique: true, where: "((import_session_id IS NOT NULL) AND (sequence IS NOT NULL))"
t.index ["import_session_id"], name: "index_imports_on_import_session_id"
t.check_constraint "checksum IS NULL OR length(checksum::text) = 64", name: "chk_imports_checksum_sha256_length"
t.check_constraint "client_chunk_id IS NULL OR btrim(client_chunk_id::text) <> ''::text", name: "chk_imports_client_chunk_id_present"
t.check_constraint "import_session_id IS NULL OR checksum IS NOT NULL", name: "chk_imports_session_checksum_present"
t.check_constraint "import_session_id IS NULL OR sequence IS NOT NULL", name: "chk_imports_session_sequence_present"
t.check_constraint "jsonb_typeof(error_details) = 'object'::text", name: "chk_imports_error_details_object"
t.check_constraint "jsonb_typeof(summary) = 'object'::text", name: "chk_imports_summary_object"
t.check_constraint "sequence IS NULL OR sequence > 0", name: "chk_imports_session_sequence_positive"
end
create_table "indexa_capital_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_number"
t.string "account_status"
t.string "account_type"
t.boolean "activities_fetch_pending", default: false
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.string "indexa_capital_account_id"
t.string "indexa_capital_authorization_id"
t.uuid "indexa_capital_item_id", null: false
t.jsonb "institution_metadata"
t.datetime "last_activities_sync"
t.datetime "last_holdings_sync"
t.string "name"
t.string "provider"
t.jsonb "raw_activities_payload", default: []
t.jsonb "raw_holdings_payload", default: []
t.jsonb "raw_payload"
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["indexa_capital_authorization_id"], name: "idx_on_indexa_capital_authorization_id_58db208d52"
t.index ["indexa_capital_item_id", "indexa_capital_account_id"], name: "index_indexa_capital_accounts_on_item_and_account_id", unique: true, where: "(indexa_capital_account_id IS NOT NULL)"
t.index ["indexa_capital_item_id"], name: "index_indexa_capital_accounts_on_indexa_capital_item_id"
end
create_table "indexa_capital_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_token"
t.datetime "created_at", null: false
t.string "document"
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.text "password"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.string "username"
t.index ["family_id"], name: "index_indexa_capital_items_on_family_id"
t.index ["status"], name: "index_indexa_capital_items_on_status"
end
create_table "insights", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "body", null: false
t.datetime "created_at", null: false
t.string "currency", default: "USD", null: false
t.string "dedup_key", null: false
t.datetime "dismissed_at"
t.jsonb "facts", default: {}, null: false
t.uuid "family_id", null: false
t.datetime "generated_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
t.string "insight_type", null: false
t.jsonb "metadata", default: {}, null: false
t.date "period_end"
t.date "period_start"
t.string "priority", default: "medium", null: false
t.datetime "read_at"
t.string "status", default: "active", null: false
t.string "title", null: false
t.datetime "updated_at", null: false
t.index ["family_id", "dedup_key"], name: "index_insights_on_family_id_and_dedup_key", unique: true
t.index ["family_id", "generated_at"], name: "index_insights_on_family_id_and_generated_at"
t.index ["family_id", "status"], name: "index_insights_on_family_id_and_status"
t.check_constraint "priority::text = ANY (ARRAY['high'::character varying::text, 'medium'::character varying::text, 'low'::character varying::text])", name: "chk_insights_priority"
t.check_constraint "status::text = ANY (ARRAY['active'::character varying::text, 'read'::character varying::text, 'dismissed'::character varying::text, 'expired'::character varying::text])", name: "chk_insights_status"
end
create_table "investments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.datetime "updated_at", null: false
end
create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "accepted_at"
t.datetime "created_at", null: false
t.string "email"
t.datetime "expires_at"
t.uuid "family_id", null: false
t.uuid "inviter_id", null: false
t.string "role"
t.string "token"
t.string "token_digest"
t.datetime "updated_at", null: false
t.index ["email", "family_id"], name: "index_invitations_on_email_and_family_id_pending", unique: true, where: "(accepted_at IS NULL)"
t.index ["email"], name: "index_invitations_on_email"
t.index ["family_id"], name: "index_invitations_on_family_id"
t.index ["inviter_id"], name: "index_invitations_on_inviter_id"
t.index ["token"], name: "index_invitations_on_token", unique: true
t.index ["token_digest"], name: "index_invitations_on_token_digest", unique: true, where: "(token_digest IS NOT NULL)"
end
create_table "invite_codes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "token", null: false
t.string "token_digest"
t.datetime "updated_at", null: false
t.index ["token"], name: "index_invite_codes_on_token", unique: true
t.index ["token_digest"], name: "index_invite_codes_on_token_digest", unique: true, where: "(token_digest IS NOT NULL)"
end
create_table "kraken_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id", null: false
t.string "account_type"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "extra", default: {}, null: false
t.jsonb "institution_metadata"
t.uuid "kraken_item_id", null: false
t.string "name"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["account_type"], name: "index_kraken_accounts_on_account_type"
t.index ["kraken_item_id", "account_id"], name: "index_kraken_accounts_on_item_and_account_id", unique: true
t.index ["kraken_item_id"], name: "index_kraken_accounts_on_kraken_item_id"
end
create_table "kraken_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_key"
t.text "api_secret"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_name"
t.string "institution_url"
t.bigint "last_nonce", default: 0, null: false
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_kraken_items_on_family_id"
t.index ["status"], name: "index_kraken_items_on_status"
end
create_table "llm_usages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.integer "cache_creation_tokens"
t.integer "cache_read_tokens"
t.integer "completion_tokens", default: 0, null: false
t.datetime "created_at", null: false
t.decimal "estimated_cost", precision: 10, scale: 6
t.uuid "family_id", null: false
t.jsonb "metadata", default: {}
t.string "model", null: false
t.string "operation", null: false
t.integer "prompt_tokens", default: 0, null: false
t.string "provider", null: false
t.integer "total_tokens", default: 0, null: false
t.datetime "updated_at", null: false
t.index ["family_id", "created_at"], name: "index_llm_usages_on_family_id_and_created_at"
t.index ["family_id", "operation"], name: "index_llm_usages_on_family_id_and_operation"
t.index ["family_id"], name: "index_llm_usages_on_family_id"
t.check_constraint "cache_creation_tokens IS NULL OR cache_creation_tokens >= 0", name: "chk_llm_usages_cache_creation_tokens_non_negative"
t.check_constraint "cache_read_tokens IS NULL OR cache_read_tokens >= 0", name: "chk_llm_usages_cache_read_tokens_non_negative"
end
create_table "loans", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.decimal "initial_balance", precision: 19, scale: 4
t.decimal "interest_rate", precision: 10, scale: 3
t.jsonb "locked_attributes", default: {}
t.string "rate_type"
t.string "subtype"
t.integer "term_months"
t.datetime "updated_at", null: false
end
create_table "lunchflow_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.boolean "holdings_supported", default: true, null: false
t.jsonb "institution_metadata"
t.uuid "lunchflow_item_id", null: false
t.string "name"
t.string "provider"
t.jsonb "raw_holdings_payload"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_lunchflow_accounts_on_account_id"
t.index ["lunchflow_item_id", "account_id"], name: "index_lunchflow_accounts_on_item_and_account_id", unique: true, where: "(account_id IS NOT NULL)"
t.index ["lunchflow_item_id"], name: "index_lunchflow_accounts_on_lunchflow_item_id"
end
create_table "lunchflow_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_key"
t.string "base_url"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_lunchflow_items_on_family_id"
t.index ["status"], name: "index_lunchflow_items_on_status"
end
create_table "merchants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "color"
t.datetime "created_at", null: false
t.uuid "family_id"
t.string "logo_url"
t.string "name", null: false
t.string "provider_merchant_id"
t.string "source"
t.string "type", null: false
t.datetime "updated_at", null: false
t.string "website_url"
t.index ["family_id", "name"], name: "index_merchants_on_family_id_and_name", unique: true, where: "((type)::text = 'FamilyMerchant'::text)"
t.index ["family_id"], name: "index_merchants_on_family_id"
t.index ["provider_merchant_id", "source"], name: "index_merchants_on_provider_merchant_id_and_source", unique: true, where: "((provider_merchant_id IS NOT NULL) AND ((type)::text = 'ProviderMerchant'::text))"
t.index ["source", "name"], name: "index_merchants_on_source_and_name", unique: true, where: "((type)::text = 'ProviderMerchant'::text)"
t.index ["type"], name: "index_merchants_on_type"
end
create_table "mercury_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id", null: false
t.string "account_status"
t.string "account_type"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.uuid "mercury_item_id", null: false
t.string "name"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.datetime "updated_at", null: false
t.index ["mercury_item_id", "account_id"], name: "index_mercury_accounts_on_item_and_account_id", unique: true
t.index ["mercury_item_id"], name: "index_mercury_accounts_on_mercury_item_id"
end
create_table "mercury_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "base_url"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.text "token"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_mercury_items_on_family_id"
t.index ["status"], name: "index_mercury_items_on_status"
end
create_table "messages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "ai_model"
t.uuid "chat_id", null: false
t.text "content"
t.datetime "created_at", null: false
t.boolean "debug", default: false
t.string "provider_id"
t.boolean "reasoning", default: false
t.string "status", default: "complete", null: false
t.string "type", null: false
t.datetime "updated_at", null: false
t.index ["chat_id"], name: "index_messages_on_chat_id"
end
create_table "mobile_devices", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "app_version"
t.datetime "created_at", null: false
t.string "device_id"
t.string "device_name"
t.string "device_type"
t.datetime "last_seen_at"
t.string "os_version"
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["user_id", "device_id"], name: "index_mobile_devices_on_user_id_and_device_id", unique: true
t.index ["user_id"], name: "index_mobile_devices_on_user_id"
end
create_table "notification_deliveries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "rule_id", null: false
t.uuid "transaction_id", null: false
t.datetime "updated_at", null: false
t.index ["rule_id", "transaction_id"], name: "index_notification_deliveries_on_rule_and_transaction", unique: true
t.index ["rule_id"], name: "index_notification_deliveries_on_rule_id"
t.index ["transaction_id"], name: "index_notification_deliveries_on_transaction_id"
end
create_table "oauth_access_grants", force: :cascade do |t|
t.bigint "application_id", null: false
t.datetime "created_at", null: false
t.integer "expires_in", null: false
t.text "redirect_uri", null: false
t.string "resource_owner_id", null: false
t.datetime "revoked_at"
t.string "scopes", default: "", null: false
t.string "token", null: false
t.index ["application_id"], name: "index_oauth_access_grants_on_application_id"
t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
create_table "oauth_access_tokens", force: :cascade do |t|
t.bigint "application_id", null: false
t.datetime "created_at", null: false
t.integer "expires_in"
t.uuid "mobile_device_id"
t.string "previous_refresh_token", default: "", null: false
t.string "refresh_token"
t.string "resource_owner_id"
t.datetime "revoked_at"
t.string "scopes"
t.string "token", null: false
t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id"
t.index ["mobile_device_id"], name: "index_oauth_access_tokens_on_mobile_device_id"
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
create_table "oauth_applications", force: :cascade do |t|
t.boolean "confidential", default: true, null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.uuid "owner_id"
t.string "owner_type"
t.text "redirect_uri", null: false
t.string "scopes", default: "", null: false
t.string "secret", null: false
t.string "uid", null: false
t.datetime "updated_at", null: false
t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type"
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
create_table "oidc_identities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "info", default: {}
t.string "issuer"
t.datetime "last_authenticated_at"
t.string "provider", null: false
t.string "uid", null: false
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["issuer"], name: "index_oidc_identities_on_issuer"
t.index ["provider", "uid"], name: "index_oidc_identities_on_provider_and_uid", unique: true
t.index ["user_id"], name: "index_oidc_identities_on_user_id"
end
create_table "onchain_wallet_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "asset_kind", null: false
t.string "chain", null: false
t.string "content_hash"
t.string "contract_address"
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.integer "decimals", default: 0, null: false
t.jsonb "extra", default: {}, null: false
t.string "name"
t.uuid "onchain_wallet_item_id", null: false
t.decimal "quantity", precision: 32, scale: 18, default: "0.0", null: false
t.jsonb "raw_movements_payload"
t.jsonb "raw_payload"
t.string "symbol", null: false
t.datetime "updated_at", null: false
t.string "wallet_address", null: false
t.index ["onchain_wallet_item_id", "chain", "wallet_address", "contract_address"], name: "index_onchain_wallet_accounts_unique_erc20", unique: true, where: "((asset_kind)::text = 'erc20'::text)"
t.index ["onchain_wallet_item_id", "chain", "wallet_address", "contract_address"], name: "index_onchain_wallet_accounts_unique_spl", unique: true, where: "((asset_kind)::text = 'spl'::text)"
t.index ["onchain_wallet_item_id", "chain", "wallet_address"], name: "index_onchain_wallet_accounts_on_item_and_address"
t.index ["onchain_wallet_item_id", "chain", "wallet_address"], name: "index_onchain_wallet_accounts_unique_native", unique: true, where: "((asset_kind)::text = 'native'::text)"
t.index ["onchain_wallet_item_id"], name: "index_onchain_wallet_accounts_on_onchain_wallet_item_id"
t.check_constraint "asset_kind::text = 'native'::text OR contract_address IS NOT NULL", name: "chk_onchain_wallet_accounts_token_has_contract"
t.check_constraint "asset_kind::text = ANY (ARRAY['native'::character varying::text, 'erc20'::character varying::text, 'spl'::character varying::text])", name: "chk_onchain_wallet_accounts_known_asset_kind"
end
create_table "onchain_wallet_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.text "etherscan_api_key"
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_onchain_wallet_items_on_family_id"
t.index ["status"], name: "index_onchain_wallet_items_on_status"
end
create_table "other_assets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.datetime "updated_at", null: false
end
create_table "other_liabilities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.datetime "updated_at", null: false
end
create_table "plaid_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "available_balance", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.string "mask"
t.string "name", null: false
t.string "plaid_id", null: false
t.uuid "plaid_item_id", null: false
t.string "plaid_subtype"
t.string "plaid_type", null: false
t.jsonb "raw_holdings_payload", default: {}
t.jsonb "raw_liabilities_payload", default: {}
t.jsonb "raw_payload", default: {}
t.jsonb "raw_transactions_payload", default: {}
t.datetime "updated_at", null: false
t.index ["plaid_item_id", "plaid_id"], name: "index_plaid_accounts_on_item_and_plaid_id", unique: true
t.index ["plaid_item_id"], name: "index_plaid_accounts_on_plaid_item_id"
end
create_table "plaid_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "access_token"
t.string "available_products", default: [], array: true
t.string "billed_products", default: [], array: true
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_id"
t.string "institution_url"
t.string "name"
t.string "next_cursor"
t.string "plaid_id", null: false
t.string "plaid_region", default: "us", null: false
t.jsonb "raw_institution_payload", default: {}
t.jsonb "raw_payload", default: {}
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_plaid_items_on_family_id"
t.index ["plaid_id"], name: "index_plaid_items_on_plaid_id", unique: true
end
create_table "properties", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "area_unit"
t.integer "area_value"
t.date "avm_last_synced_on"
t.string "avm_provider"
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "subtype"
t.datetime "updated_at", null: false
t.integer "year_built"
t.index ["avm_last_synced_on"], name: "index_properties_on_avm_provider_sync", order: "NULLS FIRST", where: "(avm_provider IS NOT NULL)"
t.check_constraint "avm_provider IS NULL OR (avm_provider::text = ANY (ARRAY['rentcast'::character varying::text, 'realie'::character varying::text]))", name: "properties_avm_provider_check"
end
create_table "provider_request_counts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.integer "count", default: 0, null: false
t.datetime "created_at", null: false
t.string "period", null: false
t.string "provider_key", null: false
t.datetime "updated_at", null: false
t.index ["provider_key", "period"], name: "index_provider_request_counts_on_provider_key_and_period", unique: true
end
create_table "push_subscriptions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "environment", null: false
t.datetime "last_registered_at", null: false
t.string "platform", default: "ios", null: false
t.string "token", null: false
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index "lower((token)::text)", name: "index_push_subscriptions_on_lower_token", unique: true
t.index ["last_registered_at"], name: "index_push_subscriptions_on_last_registered_at"
t.index ["user_id"], name: "index_push_subscriptions_on_user_id"
t.check_constraint "environment::text = ANY (ARRAY['sandbox'::character varying::text, 'production'::character varying::text])", name: "chk_push_subscriptions_environment"
t.check_constraint "platform::text = 'ios'::text", name: "chk_push_subscriptions_platform"
end
create_table "questrade_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_number"
t.string "account_status"
t.string "account_type"
t.boolean "activities_fetch_pending", default: false
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.datetime "last_activities_sync"
t.datetime "last_holdings_sync"
t.string "name"
t.string "provider"
t.string "questrade_account_id"
t.string "questrade_authorization_id"
t.uuid "questrade_item_id", null: false
t.jsonb "raw_activities_payload", default: []
t.jsonb "raw_balances_payload"
t.jsonb "raw_holdings_payload", default: []
t.jsonb "raw_payload"
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["questrade_authorization_id"], name: "index_questrade_accounts_on_questrade_authorization_id"
t.index ["questrade_item_id", "questrade_account_id"], name: "idx_on_questrade_item_id_questrade_account_id_c3e6274342", unique: true
t.index ["questrade_item_id"], name: "index_questrade_accounts_on_questrade_item_id"
end
create_table "questrade_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "api_server"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.text "refresh_token"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_questrade_items_on_family_id"
t.index ["status"], name: "index_questrade_items_on_status"
end
create_table "recurring_transactions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id"
t.decimal "amount", precision: 19, scale: 4, null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.uuid "destination_account_id"
t.decimal "expected_amount_avg", precision: 19, scale: 4
t.decimal "expected_amount_max", precision: 19, scale: 4
t.decimal "expected_amount_min", precision: 19, scale: 4
t.integer "expected_day_of_month", null: false
t.uuid "family_id", null: false
t.date "last_occurrence_date", null: false
t.boolean "manual", default: false, null: false
t.uuid "merchant_id"
t.string "name"
t.date "next_expected_date", null: false
t.integer "occurrence_count", default: 0, null: false
t.string "status", default: "active", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_recurring_transactions_on_account_id"
t.index ["destination_account_id"], name: "index_recurring_transactions_on_destination_account_id"
t.index ["family_id", "account_id", "destination_account_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_pair_merchant", unique: true, where: "((destination_account_id IS NOT NULL) AND (merchant_id IS NOT NULL))"
t.index ["family_id", "account_id", "destination_account_id", "name", "amount", "currency"], name: "idx_recurring_txns_pair_name", unique: true, where: "((destination_account_id IS NOT NULL) AND (name IS NOT NULL) AND (merchant_id IS NULL))"
t.index ["family_id", "account_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_acct_merchant", unique: true, where: "((merchant_id IS NOT NULL) AND (destination_account_id IS NULL))"
t.index ["family_id", "account_id", "name", "amount", "currency"], name: "idx_recurring_txns_acct_name", unique: true, where: "((name IS NOT NULL) AND (merchant_id IS NULL) AND (destination_account_id IS NULL))"
t.index ["family_id", "status"], name: "index_recurring_transactions_on_family_id_and_status"
t.index ["family_id"], name: "index_recurring_transactions_on_family_id"
t.index ["merchant_id"], name: "index_recurring_transactions_on_merchant_id"
t.index ["next_expected_date"], name: "index_recurring_transactions_on_next_expected_date"
t.check_constraint "destination_account_id IS NULL OR account_id IS NOT NULL", name: "chk_recurring_txns_transfer_requires_source"
t.check_constraint "destination_account_id IS NULL OR destination_account_id <> account_id", name: "chk_recurring_txns_transfer_distinct_accounts"
end
create_table "redbark_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_number"
t.string "account_status"
t.string "account_type"
t.string "connection_id"
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.boolean "ignored", default: false, null: false
t.jsonb "institution_metadata"
t.string "name", null: false
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.string "redbark_account_id", null: false
t.uuid "redbark_item_id", null: false
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["redbark_item_id", "redbark_account_id"], name: "index_redbark_accounts_on_item_and_account_id", unique: true
t.index ["redbark_item_id"], name: "index_redbark_accounts_on_redbark_item_id"
end
create_table "redbark_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "api_key", null: false
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name", null: false
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_redbark_items_on_family_id"
t.index ["status"], name: "index_redbark_items_on_status"
end
create_table "rejected_transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "inflow_transaction_id", null: false
t.uuid "outflow_transaction_id", null: false
t.datetime "updated_at", null: false
t.index ["inflow_transaction_id", "outflow_transaction_id"], name: "idx_on_inflow_transaction_id_outflow_transaction_id_412f8e7e26", unique: true
t.index ["inflow_transaction_id"], name: "index_rejected_transfers_on_inflow_transaction_id"
t.index ["outflow_transaction_id"], name: "index_rejected_transfers_on_outflow_transaction_id"
end
create_table "rule_actions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "action_type", null: false
t.datetime "created_at", null: false
t.uuid "rule_id", null: false
t.datetime "updated_at", null: false
t.string "value"
t.index ["rule_id"], name: "index_rule_actions_on_rule_id"
end
create_table "rule_conditions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "condition_type", null: false
t.datetime "created_at", null: false
t.string "operator", null: false
t.uuid "parent_id"
t.uuid "rule_id"
t.datetime "updated_at", null: false
t.string "value"
t.index ["parent_id"], name: "index_rule_conditions_on_parent_id"
t.index ["rule_id"], name: "index_rule_conditions_on_rule_id"
end
create_table "rule_runs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.text "error_message"
t.datetime "executed_at", null: false
t.string "execution_type", null: false
t.integer "pending_jobs_count", default: 0, null: false
t.uuid "rule_id", null: false
t.string "rule_name"
t.string "status", null: false
t.integer "transactions_modified", default: 0, null: false
t.integer "transactions_processed", default: 0, null: false
t.integer "transactions_queued", default: 0, null: false
t.datetime "updated_at", null: false
t.index ["executed_at"], name: "index_rule_runs_on_executed_at"
t.index ["rule_id", "executed_at"], name: "index_rule_runs_on_rule_id_and_executed_at"
t.index ["rule_id"], name: "index_rule_runs_on_rule_id"
end
create_table "rules", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.boolean "active", default: false, null: false
t.datetime "created_at", null: false
t.date "effective_date"
t.uuid "family_id", null: false
t.string "name"
t.string "resource_type", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_rules_on_family_id"
end
create_table "securities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "country_code"
t.datetime "created_at", null: false
t.string "exchange_acronym"
t.string "exchange_mic"
t.string "exchange_operating_mic"
t.datetime "failed_fetch_at"
t.integer "failed_fetch_count", default: 0, null: false
t.date "first_provider_price_on"
t.string "kind", default: "standard", null: false
t.datetime "last_health_check_at"
t.string "logo_url"
t.string "name"
t.boolean "offline", default: false, null: false
t.string "offline_reason"
t.string "price_provider"
t.string "ticker", null: false
t.datetime "updated_at", null: false
t.string "website_url"
t.index "upper((ticker)::text), COALESCE(upper((exchange_operating_mic)::text), ''::text)", name: "index_securities_on_ticker_and_exchange_operating_mic_unique", unique: true
t.index ["country_code"], name: "index_securities_on_country_code"
t.index ["exchange_operating_mic"], name: "index_securities_on_exchange_operating_mic"
t.index ["kind"], name: "index_securities_on_kind"
t.index ["price_provider", "offline_reason"], name: "index_securities_on_price_provider_and_offline_reason"
t.index ["price_provider"], name: "index_securities_on_price_provider"
t.check_constraint "kind::text = ANY (ARRAY['standard'::character varying::text, 'cash'::character varying::text])", name: "chk_securities_kind"
end
create_table "security_prices", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "currency", default: "USD", null: false
t.date "date", null: false
t.decimal "price", precision: 19, scale: 4, null: false
t.boolean "provisional", default: false, null: false
t.uuid "security_id"
t.datetime "updated_at", null: false
t.index ["security_id", "date", "currency"], name: "index_security_prices_on_security_id_and_date_and_currency", unique: true
t.index ["security_id"], name: "index_security_prices_on_security_id"
end
create_table "sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "active_impersonator_session_id"
t.datetime "created_at", null: false
t.jsonb "data", default: {}
t.string "ip_address"
t.string "ip_address_digest"
t.jsonb "prev_transaction_page_params", default: {}
t.datetime "subscribed_at"
t.datetime "updated_at", null: false
t.string "user_agent"
t.uuid "user_id", null: false
t.index ["active_impersonator_session_id"], name: "index_sessions_on_active_impersonator_session_id"
t.index ["ip_address_digest"], name: "index_sessions_on_ip_address_digest"
t.index ["user_id"], name: "index_sessions_on_user_id"
end
create_table "settings", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "value"
t.string "var", null: false
t.index ["var"], name: "index_settings_on_var", unique: true
end
create_table "simplefin_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_subtype"
t.string "account_type"
t.decimal "available_balance", precision: 19, scale: 4
t.datetime "balance_date"
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "extra"
t.string "name"
t.jsonb "org_data"
t.jsonb "raw_holdings_payload"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.uuid "simplefin_item_id", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_simplefin_accounts_on_account_id"
t.index ["simplefin_item_id", "account_id"], name: "idx_unique_sfa_per_item_and_upstream", unique: true, where: "(account_id IS NOT NULL)"
t.index ["simplefin_item_id"], name: "index_simplefin_accounts_on_simplefin_item_id"
end
create_table "simplefin_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "access_url"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_simplefin_items_on_family_id"
t.index ["institution_domain"], name: "index_simplefin_items_on_institution_domain"
t.index ["institution_id"], name: "index_simplefin_items_on_institution_id"
t.index ["institution_name"], name: "index_simplefin_items_on_institution_name"
t.index ["status"], name: "index_simplefin_items_on_status"
end
create_table "snaptrade_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_number"
t.string "account_status"
t.string "account_type"
t.boolean "activities_fetch_pending", default: false
t.string "brokerage_name"
t.decimal "cash_balance", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.jsonb "institution_metadata"
t.datetime "last_activities_sync"
t.datetime "last_holdings_sync"
t.string "name"
t.string "provider"
t.jsonb "raw_activities_payload", default: []
t.jsonb "raw_balances_payload", default: []
t.jsonb "raw_holdings_payload", default: []
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.string "snaptrade_account_id"
t.string "snaptrade_authorization_id"
t.uuid "snaptrade_item_id", null: false
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["snaptrade_item_id", "snaptrade_account_id"], name: "index_snaptrade_accounts_on_item_and_snaptrade_account_id", unique: true, where: "(snaptrade_account_id IS NOT NULL)"
t.index ["snaptrade_item_id"], name: "index_snaptrade_accounts_on_snaptrade_item_id"
end
create_table "snaptrade_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "client_id"
t.string "consumer_key"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.datetime "last_synced_at"
t.string "name"
t.text "oauth_access_token"
t.text "oauth_refresh_token"
t.string "oauth_scope"
t.datetime "oauth_token_expires_at"
t.string "oauth_token_type"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "snaptrade_user_id"
t.string "snaptrade_user_secret"
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_snaptrade_items_on_family_id"
t.index ["status"], name: "index_snaptrade_items_on_status"
end
create_table "sophtron_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id", null: false
t.string "account_number_mask"
t.string "account_status"
t.string "account_sub_type"
t.string "account_type"
t.decimal "available_balance", precision: 19, scale: 4
t.decimal "balance", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency"
t.string "customer_id"
t.jsonb "institution_metadata"
t.datetime "last_updated"
t.boolean "manual_sync", default: false, null: false
t.string "member_id"
t.string "name", null: false
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.uuid "sophtron_item_id", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_sophtron_accounts_on_account_id"
t.index ["sophtron_item_id", "account_id"], name: "idx_unique_sophtron_accounts_per_item", unique: true
t.index ["sophtron_item_id"], name: "index_sophtron_accounts_on_sophtron_item_id"
end
create_table "sophtron_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "access_key", null: false
t.string "base_url"
t.datetime "created_at", null: false
t.string "current_job_id"
t.uuid "current_job_sophtron_account_id"
t.string "customer_id"
t.string "customer_name"
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "job_status"
t.text "last_connection_error"
t.boolean "manual_sync", default: false, null: false
t.string "name"
t.boolean "pending_account_setup", default: false
t.jsonb "raw_customer_payload"
t.jsonb "raw_institution_payload"
t.jsonb "raw_job_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false
t.string "status", default: "good"
t.datetime "sync_start_date"
t.datetime "updated_at", null: false
t.string "user_id", null: false
t.string "user_institution_id"
t.index ["current_job_sophtron_account_id"], name: "index_sophtron_items_on_current_job_sophtron_account_id"
t.index ["customer_id"], name: "index_sophtron_items_on_customer_id"
t.index ["family_id"], name: "index_sophtron_items_on_family_id"
t.index ["status"], name: "index_sophtron_items_on_status"
t.index ["user_institution_id"], name: "index_sophtron_items_on_user_institution_id"
end
create_table "sso_audit_logs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "event_type", null: false
t.string "ip_address"
t.jsonb "metadata", default: {}, null: false
t.string "provider"
t.datetime "updated_at", null: false
t.string "user_agent"
t.uuid "user_id"
t.index ["created_at"], name: "index_sso_audit_logs_on_created_at"
t.index ["event_type"], name: "index_sso_audit_logs_on_event_type"
t.index ["user_id", "created_at"], name: "index_sso_audit_logs_on_user_id_and_created_at"
t.index ["user_id"], name: "index_sso_audit_logs_on_user_id"
end
create_table "sso_identity_blocks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.text "identity_label", null: false
t.string "provider", null: false
t.string "uid_digest", null: false
t.datetime "updated_at", null: false
t.index ["provider", "uid_digest"], name: "index_sso_identity_blocks_on_provider_and_uid_digest", unique: true
end
create_table "sso_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "client_id"
t.string "client_secret"
t.datetime "created_at", null: false
t.boolean "enabled", default: true, null: false
t.string "icon"
t.string "issuer"
t.string "label", null: false
t.string "name", null: false
t.string "redirect_uri"
t.jsonb "settings", default: {}, null: false
t.string "strategy", null: false
t.datetime "updated_at", null: false
t.index ["enabled"], name: "index_sso_providers_on_enabled"
t.index ["name"], name: "index_sso_providers_on_name", unique: true
end
create_table "subscriptions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "amount", precision: 19, scale: 4
t.boolean "cancel_at_period_end", default: false, null: false
t.datetime "created_at", null: false
t.string "currency"
t.datetime "current_period_ends_at"
t.uuid "family_id", null: false
t.string "interval"
t.string "status", null: false
t.string "stripe_id"
t.datetime "trial_ends_at"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_subscriptions_on_family_id", unique: true
end
create_table "syncs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "cancel_requested_at"
t.datetime "completed_at"
t.datetime "created_at", null: false
t.jsonb "data"
t.string "error"
t.datetime "failed_at"
t.uuid "parent_id"
t.datetime "pending_at"
t.string "status", default: "pending"
t.text "sync_stats"
t.uuid "syncable_id", null: false
t.string "syncable_type", null: false
t.datetime "syncing_at"
t.datetime "updated_at", null: false
t.date "window_end_date"
t.date "window_start_date"
t.index ["parent_id"], name: "index_syncs_on_parent_id"
t.index ["status"], name: "index_syncs_on_status"
t.index ["syncable_type", "syncable_id", "created_at", "id"], name: "index_syncs_on_syncable_and_created_at_and_id", order: { created_at: :desc, id: :desc }
t.index ["syncable_type", "syncable_id"], name: "index_syncs_on_syncable"
end
create_table "taggings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "tag_id", null: false
t.uuid "taggable_id"
t.string "taggable_type"
t.datetime "updated_at", null: false
t.index ["tag_id"], name: "index_taggings_on_tag_id"
t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable"
end
create_table "tags", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "color", default: "#e99537", null: false
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "name"
t.datetime "updated_at", null: false
t.index ["family_id", "name"], name: "index_tags_on_family_id_and_name", unique: true
t.index ["family_id"], name: "index_tags_on_family_id"
end
create_table "tool_calls", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "function_arguments"
t.string "function_name"
t.jsonb "function_result"
t.uuid "message_id", null: false
t.string "provider_call_id"
t.string "provider_id", null: false
t.string "type", null: false
t.datetime "updated_at", null: false
t.index ["message_id"], name: "index_tool_calls_on_message_id"
end
create_table "trades", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "currency"
t.jsonb "extra", default: {}, null: false
t.decimal "fee", precision: 19, scale: 4, default: "0.0", null: false
t.string "investment_activity_label"
t.jsonb "locked_attributes", default: {}
t.decimal "price", precision: 19, scale: 10
t.decimal "qty", precision: 24, scale: 8
t.uuid "security_id", null: false
t.datetime "updated_at", null: false
t.index ["extra"], name: "index_trades_on_extra", using: :gin
t.index ["investment_activity_label"], name: "index_trades_on_investment_activity_label"
t.index ["security_id"], name: "index_trades_on_security_id"
end
create_table "trading212_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_type"
t.decimal "cash_balance", precision: 19, scale: 4
t.datetime "created_at", null: false
t.string "currency"
t.decimal "current_balance", precision: 19, scale: 4
t.datetime "last_orders_sync"
t.datetime "last_positions_sync"
t.string "name"
t.jsonb "raw_dividends_payload", default: [], null: false
t.jsonb "raw_orders_payload", default: [], null: false
t.jsonb "raw_positions_payload", default: [], null: false
t.jsonb "raw_transactions_payload", default: [], null: false
t.string "trading212_account_id"
t.uuid "trading212_item_id", null: false
t.datetime "updated_at", null: false
t.index ["trading212_item_id", "trading212_account_id"], name: "index_trading212_accounts_on_item_and_account_id", unique: true, where: "(trading212_account_id IS NOT NULL)"
t.index ["trading212_item_id"], name: "index_trading212_accounts_on_trading212_item_id"
end
create_table "trading212_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "api_key"
t.string "api_secret"
t.datetime "created_at", null: false
t.string "currency"
t.string "environment", default: "live", null: false
t.uuid "family_id", null: false
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_instruments_payload", default: [], null: false
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_trading212_items_on_family_id"
t.index ["status"], name: "index_trading212_items_on_status"
end
create_table "transactions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "category_id"
t.datetime "created_at", null: false
t.string "external_id"
t.jsonb "extra", default: {}, null: false
t.string "investment_activity_label"
t.string "kind", default: "standard", null: false
t.jsonb "locked_attributes", default: {}
t.uuid "merchant_id"
t.uuid "transfer_id"
t.datetime "updated_at", null: false
t.index "(((extra -> 'goal'::text) ->> 'pledge_id'::text))", name: "ix_transactions_extra_goal_pledge_id", unique: true, where: "(((extra -> 'goal'::text) ->> 'pledge_id'::text) IS NOT NULL)"
t.index ["category_id"], name: "index_transactions_on_category_id"
t.index ["external_id"], name: "index_transactions_on_external_id"
t.index ["extra"], name: "index_transactions_on_extra", using: :gin
t.index ["investment_activity_label"], name: "index_transactions_on_investment_activity_label"
t.index ["kind"], name: "index_transactions_on_kind"
t.index ["merchant_id"], name: "index_transactions_on_merchant_id"
t.index ["transfer_id"], name: "index_transactions_on_transfer_id"
end
create_table "transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "amount", precision: 19, scale: 4, default: "0.0", null: false
t.datetime "created_at", null: false
t.uuid "inflow_transaction_id", null: false
t.text "notes"
t.uuid "outflow_transaction_id", null: false
t.string "status", default: "pending", null: false
t.datetime "updated_at", null: false
t.index ["inflow_transaction_id", "outflow_transaction_id"], name: "idx_on_inflow_transaction_id_outflow_transaction_id_8cd07a28bd", unique: true
t.index ["inflow_transaction_id"], name: "index_transfers_on_inflow_transaction_id"
t.index ["outflow_transaction_id"], name: "index_transfers_on_outflow_transaction_id"
t.index ["status"], name: "index_transfers_on_status"
t.check_constraint "amount >= 0::numeric", name: "check_transfer_amount_non_negative"
end
create_table "up_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "account_id"
t.string "account_status"
t.string "account_type"
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.boolean "ignored", default: false, null: false
t.jsonb "institution_metadata"
t.string "name", null: false
t.string "ownership_type"
t.string "provider"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.date "sync_start_date"
t.uuid "up_item_id", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_up_accounts_on_account_id"
t.index ["up_item_id", "account_id"], name: "index_up_accounts_on_item_and_account_id", unique: true, where: "(account_id IS NOT NULL)"
t.index ["up_item_id"], name: "index_up_accounts_on_up_item_id"
end
create_table "up_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.text "access_token"
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.string "institution_color"
t.string "institution_domain"
t.string "institution_id"
t.string "institution_name"
t.string "institution_url"
t.string "name"
t.boolean "pending_account_setup", default: false, null: false
t.jsonb "raw_institution_payload"
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.date "sync_start_date"
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_up_items_on_family_id"
t.index ["status"], name: "index_up_items_on_status"
end
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.boolean "active", default: true, null: false
t.boolean "ai_enabled", default: false, null: false
t.datetime "created_at", null: false
t.uuid "default_account_id"
t.string "default_account_order", default: "name_asc"
t.string "default_period", default: "last_30_days", null: false
t.string "email"
t.uuid "family_id", null: false
t.string "first_name"
t.text "goals", default: [], array: true
t.string "last_name"
t.uuid "last_viewed_chat_id"
t.string "locale"
t.datetime "onboarded_at"
t.string "otp_backup_codes", default: [], array: true
t.boolean "otp_required", default: false, null: false
t.string "otp_secret"
t.string "password_digest"
t.jsonb "preferences", default: {}, null: false
t.string "role", default: "member", null: false
t.datetime "rule_prompt_dismissed_at"
t.boolean "rule_prompts_disabled", default: false
t.datetime "set_onboarding_goals_at"
t.datetime "set_onboarding_preferences_at"
t.boolean "show_ai_sidebar", default: true
t.boolean "show_sidebar", default: true
t.string "theme", default: "system"
t.string "ui_layout"
t.string "unconfirmed_email"
t.datetime "updated_at", null: false
t.string "webauthn_id"
t.index ["default_account_id"], name: "index_users_on_default_account_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["family_id"], name: "index_users_on_family_id"
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
t.index ["locale"], name: "index_users_on_locale"
t.index ["otp_secret"], name: "index_users_on_otp_secret", unique: true, where: "(otp_secret IS NOT NULL)"
t.index ["preferences"], name: "index_users_on_preferences", using: :gin
t.index ["webauthn_id"], name: "index_users_on_webauthn_id", unique: true, where: "(webauthn_id IS NOT NULL)"
end
create_table "valuations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "kind", default: "reconciliation", null: false
t.jsonb "locked_attributes", default: {}
t.datetime "updated_at", null: false
end
create_table "vehicles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "locked_attributes", default: {}
t.string "make"
t.string "mileage_unit"
t.integer "mileage_value"
t.string "model"
t.string "subtype"
t.datetime "updated_at", null: false
t.integer "year"
end
create_table "webauthn_credentials", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "credential_id", null: false
t.datetime "last_used_at"
t.string "nickname", null: false
t.text "public_key", null: false
t.bigint "sign_count", default: 0, null: false
t.string "transports", default: [], null: false, array: true
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["credential_id"], name: "index_webauthn_credentials_on_credential_id", unique: true
t.index ["user_id"], name: "index_webauthn_credentials_on_user_id"
t.check_constraint "sign_count >= 0", name: "chk_webauthn_credentials_sign_count_non_negative"
end
create_table "wise_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "balance_id", null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.decimal "current_balance", precision: 19, scale: 4
t.string "name"
t.jsonb "raw_payload"
t.jsonb "raw_transactions_payload"
t.decimal "reserved_balance", precision: 19, scale: 4
t.datetime "updated_at", null: false
t.uuid "wise_item_id", null: false
t.index ["wise_item_id", "balance_id"], name: "index_wise_accounts_on_wise_item_id_and_balance_id", unique: true
t.index ["wise_item_id"], name: "index_wise_accounts_on_wise_item_id"
end
create_table "wise_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "family_id", null: false
t.boolean "import_all_history", default: false, null: false
t.string "name", null: false
t.boolean "pending_account_setup", default: false, null: false
t.string "profile_id", null: false
t.string "profile_type", null: false
t.jsonb "raw_payload"
t.boolean "scheduled_for_deletion", default: false, null: false
t.string "status", default: "good", null: false
t.datetime "sync_start_date"
t.text "token", null: false
t.datetime "updated_at", null: false
t.index ["family_id", "profile_id"], name: "index_wise_items_on_family_id_and_profile_id", unique: true
t.index ["family_id"], name: "index_wise_items_on_family_id"
t.index ["status"], name: "index_wise_items_on_status"
end
add_foreign_key "account_providers", "accounts", on_delete: :cascade
add_foreign_key "account_shares", "accounts"
add_foreign_key "account_shares", "users"
add_foreign_key "account_statements", "accounts", column: "suggested_account_id", on_delete: :nullify
add_foreign_key "account_statements", "accounts", on_delete: :nullify
add_foreign_key "account_statements", "families", on_delete: :cascade
add_foreign_key "accounts", "families"
add_foreign_key "accounts", "imports"
add_foreign_key "accounts", "plaid_accounts"
add_foreign_key "accounts", "simplefin_accounts"
add_foreign_key "accounts", "users", column: "owner_id", on_delete: :nullify
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "akahu_accounts", "akahu_items"
add_foreign_key "akahu_items", "families"
add_foreign_key "api_keys", "users"
add_foreign_key "balances", "accounts", on_delete: :cascade
add_foreign_key "binance_accounts", "binance_items"
add_foreign_key "binance_items", "families"
add_foreign_key "brex_accounts", "brex_items"
add_foreign_key "brex_items", "families"
add_foreign_key "budget_categories", "budgets", on_delete: :cascade
add_foreign_key "budget_categories", "categories"
add_foreign_key "budget_shares", "users", column: "owner_id"
add_foreign_key "budget_shares", "users", column: "viewer_id"
add_foreign_key "budgets", "families"
add_foreign_key "budgets", "users", on_delete: :cascade
add_foreign_key "categories", "families"
add_foreign_key "chats", "users"
add_foreign_key "coinbase_accounts", "coinbase_items"
add_foreign_key "coinbase_items", "families"
add_foreign_key "coinstats_accounts", "coinstats_items"
add_foreign_key "coinstats_items", "families"
add_foreign_key "debug_log_entries", "account_providers", on_delete: :nullify
add_foreign_key "debug_log_entries", "accounts", on_delete: :nullify
add_foreign_key "debug_log_entries", "families", on_delete: :nullify
add_foreign_key "debug_log_entries", "users", on_delete: :nullify
add_foreign_key "enable_banking_accounts", "enable_banking_items"
add_foreign_key "enable_banking_items", "families"
add_foreign_key "entries", "account_statements", column: "reconciled_by_statement_id", on_delete: :nullify
add_foreign_key "entries", "accounts", on_delete: :cascade
add_foreign_key "entries", "entries", column: "parent_entry_id", on_delete: :cascade
add_foreign_key "entries", "imports"
add_foreign_key "eval_results", "eval_runs"
add_foreign_key "eval_results", "eval_samples"
add_foreign_key "eval_runs", "eval_datasets"
add_foreign_key "eval_samples", "eval_datasets"
add_foreign_key "family_documents", "families"
add_foreign_key "family_exports", "families"
add_foreign_key "family_merchant_associations", "families"
add_foreign_key "family_merchant_associations", "merchants"
add_foreign_key "goal_accounts", "accounts", on_delete: :restrict
add_foreign_key "goal_accounts", "goals", on_delete: :cascade
add_foreign_key "goal_pledges", "accounts", on_delete: :restrict
add_foreign_key "goal_pledges", "goals", on_delete: :cascade
add_foreign_key "goal_pledges", "transactions", column: "matched_transaction_id", on_delete: :nullify
add_foreign_key "goals", "families", on_delete: :cascade
add_foreign_key "holdings", "account_providers"
add_foreign_key "holdings", "accounts", on_delete: :cascade
add_foreign_key "holdings", "securities"
add_foreign_key "holdings", "securities", column: "provider_security_id"
add_foreign_key "ibkr_accounts", "ibkr_items"
add_foreign_key "ibkr_items", "families"
add_foreign_key "impersonation_session_logs", "impersonation_sessions"
add_foreign_key "impersonation_sessions", "users", column: "impersonated_id"
add_foreign_key "impersonation_sessions", "users", column: "impersonator_id"
add_foreign_key "import_rows", "imports"
add_foreign_key "import_sessions", "families"
add_foreign_key "import_source_mappings", "families"
add_foreign_key "import_source_mappings", "import_sessions", column: ["import_session_id", "family_id"], primary_key: ["id", "family_id"], name: "fk_import_source_mappings_session_family", on_delete: :cascade
add_foreign_key "imports", "account_statements", on_delete: :nullify
add_foreign_key "imports", "families"
add_foreign_key "imports", "import_sessions", column: ["import_session_id", "family_id"], primary_key: ["id", "family_id"], name: "fk_imports_session_family", on_delete: :cascade
add_foreign_key "indexa_capital_accounts", "indexa_capital_items"
add_foreign_key "indexa_capital_items", "families"
add_foreign_key "insights", "families"
add_foreign_key "invitations", "families"
add_foreign_key "invitations", "users", column: "inviter_id"
add_foreign_key "kraken_accounts", "kraken_items"
add_foreign_key "kraken_items", "families"
add_foreign_key "llm_usages", "families"
add_foreign_key "lunchflow_accounts", "lunchflow_items"
add_foreign_key "lunchflow_items", "families"
add_foreign_key "merchants", "families"
add_foreign_key "mercury_accounts", "mercury_items"
add_foreign_key "mercury_items", "families"
add_foreign_key "messages", "chats"
add_foreign_key "mobile_devices", "users"
add_foreign_key "notification_deliveries", "rules", on_delete: :cascade
add_foreign_key "notification_deliveries", "transactions", on_delete: :cascade
add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
add_foreign_key "oidc_identities", "users"
add_foreign_key "onchain_wallet_accounts", "onchain_wallet_items"
add_foreign_key "onchain_wallet_items", "families"
add_foreign_key "plaid_accounts", "plaid_items"
add_foreign_key "plaid_items", "families"
add_foreign_key "push_subscriptions", "users"
add_foreign_key "questrade_accounts", "questrade_items"
add_foreign_key "questrade_items", "families"
add_foreign_key "recurring_transactions", "accounts", column: "destination_account_id", on_delete: :cascade
add_foreign_key "recurring_transactions", "accounts", on_delete: :cascade
add_foreign_key "recurring_transactions", "families"
add_foreign_key "recurring_transactions", "merchants"
add_foreign_key "redbark_accounts", "redbark_items"
add_foreign_key "redbark_items", "families"
add_foreign_key "rejected_transfers", "transactions", column: "inflow_transaction_id", on_delete: :cascade
add_foreign_key "rejected_transfers", "transactions", column: "outflow_transaction_id", on_delete: :cascade
add_foreign_key "rule_actions", "rules"
add_foreign_key "rule_conditions", "rule_conditions", column: "parent_id"
add_foreign_key "rule_conditions", "rules"
add_foreign_key "rule_runs", "rules"
add_foreign_key "rules", "families"
add_foreign_key "security_prices", "securities"
add_foreign_key "sessions", "impersonation_sessions", column: "active_impersonator_session_id"
add_foreign_key "sessions", "users"
add_foreign_key "simplefin_accounts", "simplefin_items"
add_foreign_key "simplefin_items", "families"
add_foreign_key "snaptrade_accounts", "snaptrade_items"
add_foreign_key "snaptrade_items", "families"
add_foreign_key "sophtron_accounts", "sophtron_items"
add_foreign_key "sophtron_items", "families"
add_foreign_key "sso_audit_logs", "users"
add_foreign_key "subscriptions", "families"
add_foreign_key "syncs", "syncs", column: "parent_id"
add_foreign_key "taggings", "tags"
add_foreign_key "tags", "families"
add_foreign_key "tool_calls", "messages"
add_foreign_key "trades", "securities"
add_foreign_key "trading212_accounts", "trading212_items"
add_foreign_key "trading212_items", "families"
add_foreign_key "transactions", "categories", on_delete: :nullify
add_foreign_key "transactions", "merchants"
add_foreign_key "transactions", "transfers"
add_foreign_key "transfers", "transactions", column: "inflow_transaction_id", on_delete: :cascade
add_foreign_key "transfers", "transactions", column: "outflow_transaction_id", on_delete: :cascade
add_foreign_key "up_accounts", "up_items"
add_foreign_key "up_items", "families"
add_foreign_key "users", "accounts", column: "default_account_id", on_delete: :nullify
add_foreign_key "users", "chats", column: "last_viewed_chat_id"
add_foreign_key "users", "families"
add_foreign_key "webauthn_credentials", "users"
add_foreign_key "wise_accounts", "wise_items", on_delete: :cascade
add_foreign_key "wise_items", "families"
end