Manual Account Entry and Import#
Manual accounts are accounts with no linked banking provider — they have no Plaid, SimpleFIN, or other account_providers record. The Account model's manual scope identifies them by the absence of those associations. There are two primary paths for getting data into a manual account:
- UI-driven manual entry — create an account with an opening balance and enter transactions directly.
- File-based import — upload a file in one of several supported formats.
After any data change, balance history is recalculated using the forward strategy (vs. the reverse strategy used by linked/synced accounts).
Manual Account Creation (UI Entry)#
The AccountableResource concern provides the shared create/update actions for all account type controllers (Depository, Investment, CreditCard, Loan, Property, Vehicle, etc.).
Creation flow:
opening_balance_dateis read from the form; defaults to 2 years ago if absent .- Calls
Account.create_and_sync(params, opening_balance_date:), which creates an opening-anchor valuation viaAccount::OpeningBalanceManagerand then enqueues a balance sync. - Permitted params:
name,balance,currency,subtype,opening_balance_date,institution_name,institution_domain,notes,exclude_from_reports.
Balance updates on an existing account go through Account#set_current_balance, which creates a new reconciliation Valuation entry rather than mutating the original opening anchor .
File Import System#
Base Model#
Import is the polymorphic base class for all import types. It handles CSV parsing (with configurable column separator, number format, and encoding), date-format detection, duplicate row skipping, and the publish/revert lifecycle :
| Status | Meaning |
|---|---|
pending | Created, not yet submitted |
importing | ImportJob running |
complete | Successfully imported |
failed | Import raised an error |
reverting | RevertImportJob running |
revert_failed | Revert raised an error |
Lifecycle: upload → Import::Preflight validation → configure column mappings → publish_later → ImportJob calls import! → family.sync_later . Reverting destroys all accounts and entries the import created, then re-syncs .
Controllers: ImportsController drives the full publish/revert lifecycle; Import::UploadsController handles file uploads and format validation (QIF requires an account selection step; SureImport triggers preflight validation).
Mapping models: Import::AccountMapping, Import::CategoryMapping, Import::Row (signed amount conversion, tag parsing), Import::Preflight (pre-flight validation without persisting).
Supported Formats#
| Importer | Format | Notes |
|---|---|---|
TransactionImport | CSV | Generic; duplicate detection, account/category/tag mapping |
MintImport | CSV | Columns: Date, Amount, Account Name, Description, Category, Labels |
YnabImport | CSV | Supports modern web + legacy classic formats; merges split category columns |
QifImport | QIF | Investment account + trade actions; opening balance management; skips CSV workflow |
ActualImport | CSV | Actual Budget export; hierarchical category groups |
SureImport | NDJSON | Bulk export/import of all entity types (Account, Transaction, Trade, Holding, etc.); chunked via ImportSession |
PdfImport | AI-powered bank statement extraction; skips CSV workflow | |
TradeImport | CSV | Security buy/sell trades |
Chunked NDJSON (SureImport)#
Large NDJSON exports are split into chunks. ImportSession orchestrates multi-chunk uploads: it validates sequence order, verifies SHA-256 checksums per chunk, enforces total row count limits, and tracks readback verification (not_verified → matched / mismatch / failed). ImportSessionJob publishes each chunk in sequence.
Forward-Strategy Balance Calculation#
Manual accounts use the forward strategy for balance history. In Account::Syncer#perform_sync, the strategy is chosen as:
linked account → :reverse— start from today's provider-reported balance and work backwardmanual account → :forward— start from the opening anchor and project forward
Balance::ForwardCalculator iterates day-by-day from calc_start_date to calc_end_date, applying daily transaction flows and market-value changes to carry the balance forward .
Valuation overrides: If a Valuation entry exists for a date (e.g., a user-entered reconciliation), that day's ending balance is taken directly from the valuation rather than being derived from flows .
Incremental mode: When window_start_date is provided, the calculator seeds from the persisted Balance row for window_start_date - 1 instead of replaying from the opening anchor. It falls back to full recalculation when :
- No prior persisted balance exists for that date
- The prior balance has a non-zero non-cash component (investment holdings are always fully rematerialized)
- The account has multi-currency entries or a foreign currency
Balance::Materializer orchestrates the forward calculation, persists the resulting Balance rows, and updates the account's balance column. Manual accounts do not use a current_anchor — their current balance is derived from the running forward calculation .