Files
sure/db/schema.rb
T
Igor OliveiraandJuan José Mata 05a787330d Fix Wise incoming transfers by implementing Strong Customer Authentication (#3391)
* Support Wise Strong Customer Authentication for balance statements

The balance-statement endpoint always 403s because it requires a signed
one-time-token challenge (SCA) that Sure never implemented, so every sync
silently fell back to /v1/transfers — an outgoing-only endpoint — meaning
incoming payments into a Wise balance never synced.

Adds a per-item RSA keypair (private key encrypted at rest) that signs the
SCA challenge and retries the statement request once, plus a settings UI
to generate the keypair and register its public key with Wise.

Fixes #3384

* Backfill incoming statements past legacy transfers; fix review nits

Backfill: once statements start succeeding for an account that already has
legacy /v1/transfers rows, the fetch window was clamped to end the day
before the oldest legacy transfer, so the window where incoming payments
were actually missing (the recent window transfers already "covered" with
outgoing-only data) was never re-fetched. Statement rows in that overlap
are now kept when they're incoming and dropped when outgoing, since the
legacy transfer rows already account for the outgoing side.

Also: replace the inline onclick handler on the SCA public key display with
the existing clipboard Stimulus controller (copy button, matching the API
key reveal pattern), and correct the regenerate-keypair confirmation text,
which implied local regeneration revokes the key with Wise -- it doesn't;
the old public key stays valid there until removed manually.

* Avoid double-booking internal cross-currency conversions on statement backfill

The backfilled statement fetch's outgoing/incoming filter only looked at
sign: a positive (credit) statement row was always kept in the legacy
overlap window. But a legacy transfer row can itself be incoming for this
account when it's the target side of a conversion between two of the
profile's own balances -- Wise already fully captures both legs of those
via /v1/transfers, unlike genuine external payments.

Now an incoming statement row in the overlap window is dropped only when
it matches a known incoming legacy transfer's date and amount, so internal
conversions aren't duplicated while external incoming payments (no legacy
counterpart) still backfill correctly.

* Never drop an incoming statement row on a date/amount heuristic

The previous fix dropped an incoming statement row in the legacy-overlap
window when it matched a known incoming legacy transfer's date and amount,
to avoid double-booking internal cross-currency conversions. But nothing
short of an endpoint-proven correlation id can tell that apart from a
genuine external payment that happens to share the same date and amount --
and silently losing a real transaction is worse than an occasional visible,
user-correctable duplicate. Incoming rows are kept unconditionally again.

Instead, bound the exposure at the source: the /v1/transfers fallback now
stops running for an account as soon as it has a successful statement row,
since statements alone cover both directions from then on. This leaves only
a narrow, one-time window (the initial backfill of historical internal
conversions) where a duplicate can occur, rather than an indefinite one.

* Gate the transfer fallback per-account, not per-item

legacy_transfer_import_needed? decides whether to fetch /v1/transfers at
all, but that decision is profile-wide -- true as soon as any one account
still needs the fallback. store_transfers_per_account then merged those
transfers into every currency-matching account by currency alone, with no
check for whether that specific account had already migrated to
statements. A still-legacy account in one currency was enough to make an
already-migrated account in the same currency re-absorb a movement its own
statements already had, double-booked under a different key.

account_transfers is now cleared for any account that already has
statement rows, regardless of why the profile-wide fetch ran.

* Handle SCA controller errors, corrupted keys, and adapter test coverage

- generate_sca_keypair now rescues like every other mutating action in
  this controller, logging and re-rendering the panel with an error
  instead of a raw 500 if the update ever raises.
- sca_configured? now depends on sca_public_key actually parsing, not just
  sca_private_key being present, so a corrupted/unparsable stored key
  (encryption misconfig, manual DB edit) falls back to the "generate a
  keypair" UI state instead of rendering a public key box around nothing.
- Added test/models/provider/wise_adapter_test.rb, which had no coverage
  at all, to cover build_provider's family/wise_item_id resolution and
  that sca_private_key actually reaches the constructed Provider::Wise.

* Add logging to Wise sync

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-09-05 08:37:32 +02:00

2743 lines
137 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_09_04_201444) 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.string "idempotency_key"
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", "idempotency_key"], name: "index_entries_on_account_and_idempotency_key", unique: true, where: "(idempotency_key IS NOT NULL)"
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 "bills_feed_token"
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.index ["bills_feed_token"], name: "index_families_on_bills_feed_token", unique: true
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, 'Category'::character varying, 'Tag'::character varying, 'Merchant'::character varying, 'RecurringTransaction'::character varying, 'RecurringOccurrence'::character varying, 'Transaction'::character varying, 'Budget'::character varying, 'Security'::character varying, 'Rule'::character varying]::text[])", name: "chk_import_source_mappings_source_type"
t.check_constraint "target_type::text = ANY (ARRAY['Account'::character varying, 'Category'::character varying, 'Tag'::character varying, 'Merchant'::character varying, 'RecurringTransaction'::character varying, 'RecurringOccurrence'::character varying, 'Transaction'::character varying, 'Budget'::character varying, 'Security'::character varying, '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 "recurrence_rules", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "day_of_month"
t.string "frequency", null: false
t.integer "interval", default: 1, null: false
t.integer "month_of_year"
t.integer "position", default: 0, null: false
t.uuid "recurring_transaction_id", null: false
t.datetime "updated_at", null: false
t.integer "weekday"
t.integer "weekday_ordinal"
t.index ["recurring_transaction_id", "position"], name: "idx_on_recurring_transaction_id_position_62f0900d95", unique: true
t.check_constraint "NOT (day_of_month IS NOT NULL AND weekday IS NOT NULL)", name: "chk_recurrence_rules_single_day_spec"
t.check_constraint "\"interval\" > 0", name: "chk_recurrence_rules_interval_positive"
t.check_constraint "day_of_month IS NULL OR day_of_month >= '-1'::integer AND day_of_month <= 31 AND day_of_month <> 0", name: "chk_recurrence_rules_day_of_month_range"
t.check_constraint "frequency::text = ANY (ARRAY['weekly'::character varying, 'monthly'::character varying, 'yearly'::character varying]::text[])", name: "chk_recurrence_rules_frequency"
t.check_constraint "month_of_year IS NULL OR month_of_year >= 1 AND month_of_year <= 12", name: "chk_recurrence_rules_month_of_year_range"
t.check_constraint "weekday IS NULL OR weekday >= 0 AND weekday <= 6", name: "chk_recurrence_rules_weekday_range"
t.check_constraint "weekday_ordinal IS NULL OR weekday IS NOT NULL", name: "chk_recurrence_rules_ordinal_requires_weekday"
t.check_constraint "weekday_ordinal IS NULL OR weekday_ordinal >= '-1'::integer AND weekday_ordinal <= 5 AND weekday_ordinal <> 0", name: "chk_recurrence_rules_weekday_ordinal_range"
end
create_table "recurring_allocations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.decimal "allocated_amount", precision: 19, scale: 4, null: false
t.datetime "created_at", null: false
t.string "currency", null: false
t.uuid "entry_id"
t.decimal "match_confidence", precision: 5, scale: 4
t.jsonb "match_signals", default: {}, null: false
t.date "paid_on"
t.uuid "recurring_occurrence_id", null: false
t.string "source", null: false
t.decimal "source_amount", precision: 19, scale: 4
t.string "source_currency"
t.string "state", default: "confirmed", null: false
t.datetime "updated_at", null: false
t.index ["entry_id"], name: "index_recurring_allocations_on_entry_id"
t.index ["recurring_occurrence_id", "entry_id"], name: "idx_recurring_allocations_entry_once", unique: true, where: "(entry_id IS NOT NULL)"
t.index ["recurring_occurrence_id"], name: "index_recurring_allocations_on_recurring_occurrence_id"
t.check_constraint "allocated_amount > 0::numeric", name: "chk_recurring_allocations_amount_positive"
t.check_constraint "source::text = ANY (ARRAY['auto_matched'::character varying, 'user_confirmed'::character varying, 'user_created'::character varying]::text[])", name: "chk_recurring_allocations_source"
t.check_constraint "state::text = ANY (ARRAY['suggested'::character varying, 'confirmed'::character varying]::text[])", name: "chk_recurring_allocations_state"
end
create_table "recurring_match_rejections", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.uuid "entry_id", null: false
t.uuid "recurring_transaction_id", null: false
t.datetime "updated_at", null: false
t.index ["entry_id"], name: "index_recurring_match_rejections_on_entry_id"
t.index ["recurring_transaction_id", "entry_id"], name: "idx_recurring_match_rejections_pair", unique: true
end
create_table "recurring_occurrences", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "closed_at"
t.string "closed_source"
t.datetime "created_at", null: false
t.string "currency", null: false
t.date "due_on", null: false
t.decimal "expected_amount", precision: 19, scale: 4
t.uuid "family_id", null: false
t.text "notes"
t.date "original_due_on", null: false
t.uuid "recurring_transaction_id", null: false
t.date "snoozed_until"
t.string "status", default: "scheduled", null: false
t.datetime "updated_at", null: false
t.index ["family_id", "due_on"], name: "index_recurring_occurrences_on_family_id_and_due_on"
t.index ["family_id", "status", "due_on"], name: "index_recurring_occurrences_on_family_id_and_status_and_due_on"
t.index ["id", "currency"], name: "idx_recurring_occurrences_id_currency", unique: true
t.index ["recurring_transaction_id", "original_due_on"], name: "idx_recurring_occurrences_identity", unique: true
t.check_constraint "(status::text = 'scheduled'::text) = (closed_at IS NULL)", name: "chk_recurring_occurrences_closed_state"
t.check_constraint "closed_source IS NULL OR (closed_source::text = ANY (ARRAY['auto'::character varying, 'user'::character varying]::text[]))", name: "chk_recurring_occurrences_closed_source"
t.check_constraint "status::text = ANY (ARRAY['scheduled'::character varying, 'paid'::character varying, 'skipped'::character varying, 'missed'::character varying]::text[])", name: "chk_recurring_occurrences_status"
end
create_table "recurring_price_changes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "currency", null: false
t.date "effective_on", null: false
t.uuid "entry_id"
t.decimal "new_amount", precision: 19, scale: 4, null: false
t.decimal "previous_amount", precision: 19, scale: 4, null: false
t.uuid "recurring_transaction_id", null: false
t.string "source", null: false
t.datetime "updated_at", null: false
t.index ["entry_id"], name: "index_recurring_price_changes_on_entry_id"
t.index ["recurring_transaction_id", "effective_on"], name: "idx_recurring_price_changes_identity", unique: true
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.string "amount_strategy", default: "fixed", null: false
t.decimal "amount_tolerance_pct", precision: 5, scale: 2, default: "7.5", null: false
t.date "anchor_date"
t.boolean "autopay", default: false, null: false
t.string "bill_type", default: "bill", null: false
t.date "cancelled_on"
t.uuid "category_id"
t.datetime "created_at", null: false
t.string "currency", null: false
t.string "dedup_scope", default: "", null: false
t.uuid "destination_account_id"
t.integer "end_after_count"
t.string "end_mode", default: "never", null: false
t.date "end_on"
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.string "holiday_calendar"
t.date "last_occurrence_date", null: false
t.boolean "manual", default: false, null: false
t.integer "match_days_early", default: 2, null: false
t.integer "match_days_late", default: 7, null: false
t.jsonb "matcher_hints", default: {}, null: false
t.uuid "merchant_id"
t.string "name"
t.date "next_expected_date", null: false
t.text "notes"
t.integer "notify_days_before"
t.integer "occurrence_count", default: 0, null: false
t.integer "overdue_grace_days"
t.string "payment_url"
t.date "renews_on"
t.uuid "replaced_by_id"
t.string "status", default: "active", null: false
t.date "trial_ends_on"
t.integer "upcoming_window_days"
t.datetime "updated_at", null: false
t.string "weekend_adjust", default: "none", null: false
t.index ["account_id"], name: "index_recurring_transactions_on_account_id"
t.index ["category_id"], name: "index_recurring_transactions_on_category_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", "dedup_scope"], 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", "dedup_scope"], 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", "dedup_scope"], 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", "dedup_scope"], 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 "trade_republic_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.boolean "holdings_snapshot_complete", default: false, null: false
t.string "kind", default: "portfolio", null: false
t.datetime "last_positions_sync"
t.string "name"
t.jsonb "raw_positions_payload", default: [], null: false
t.jsonb "raw_timeline_payload", default: [], null: false
t.string "trade_republic_account_id"
t.uuid "trade_republic_item_id", null: false
t.datetime "updated_at", null: false
t.index ["trade_republic_item_id", "kind"], name: "idx_on_trade_republic_item_id_kind_3b60cc72fb", unique: true
t.index ["trade_republic_item_id", "trade_republic_account_id"], name: "index_trade_republic_accounts_on_item_and_account_id", unique: true, where: "(trade_republic_account_id IS NOT NULL)"
t.index ["trade_republic_item_id"], name: "index_trade_republic_accounts_on_trade_republic_item_id"
end
create_table "trade_republic_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.string "currency"
t.uuid "family_id", null: false
t.string "name"
t.string "newest_event_id"
t.boolean "pending_account_setup", default: false, null: false
t.text "pending_login_state"
t.string "phone_number"
t.boolean "scheduled_for_deletion", default: false, null: false
t.text "session_blob"
t.string "status", default: "good", null: false
t.datetime "updated_at", null: false
t.index ["family_id"], name: "index_trade_republic_items_on_family_id"
t.index ["status"], name: "index_trade_republic_items_on_status"
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.datetime "last_login_at"
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.integer "sessions_count", default: 0, null: 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.text "sca_private_key"
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 "recurrence_rules", "recurring_transactions", on_delete: :cascade
add_foreign_key "recurring_allocations", "entries", on_delete: :nullify
add_foreign_key "recurring_allocations", "recurring_occurrences", column: ["recurring_occurrence_id", "currency"], primary_key: ["id", "currency"], name: "fk_recurring_allocations_currency_matches_occurrence", on_delete: :cascade
add_foreign_key "recurring_allocations", "recurring_occurrences", on_delete: :cascade
add_foreign_key "recurring_match_rejections", "entries", on_delete: :cascade
add_foreign_key "recurring_match_rejections", "recurring_transactions", on_delete: :cascade
add_foreign_key "recurring_occurrences", "families", on_delete: :cascade
add_foreign_key "recurring_occurrences", "recurring_transactions", on_delete: :cascade
add_foreign_key "recurring_price_changes", "entries", on_delete: :nullify
add_foreign_key "recurring_price_changes", "recurring_transactions", on_delete: :cascade
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", "categories", on_delete: :nullify
add_foreign_key "recurring_transactions", "families"
add_foreign_key "recurring_transactions", "merchants"
add_foreign_key "recurring_transactions", "recurring_transactions", column: "replaced_by_id", on_delete: :nullify
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 "trade_republic_accounts", "trade_republic_items"
add_foreign_key "trade_republic_items", "families"
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