Gallery › Money › Money transactions (illustrative)

Money transactions (illustrative)

Transactions grouped by kind and category, a table with a sum per category, the same rows by entity, and a form that recategorises one. Illustrative rows only: money's synthetic test fixture, never real account data.

Moneylisttablerecord4 views of one transactions.json

The .base file and its data

Every tab above is one entry of this file; the views read these rows. ⋯ in the toolbar copies the current view as YAML.

# Transactions, ILLUSTRATIVE: money's test fixture (synthetic rows, no real account data), each booked by money's own
# ledger code. Obsidian Bases grammar plus data-ui's keys (docs/CONTRACT.md §5).
data: transactions.json
facets:
  - entity
  - group
  - category
views:
  - type: list
    name: Recent
    order: [name, amount, date, entity]
    groupBy: { property: group, direction: ASC }
    subGroupBy: { property: category, direction: ASC }
    sort:
      - { property: date, direction: DESC }
    markers: none
    separator: " · "
  - type: table
    name: By category
    order: [name, date, amount, entity, account, how]
    groupBy: { property: category, direction: ASC }
    sort:
      - { property: date, direction: DESC }
    summaries: { amount: Sum }
  - type: list
    name: By entity
    order: [name, amount, date, category]
    groupBy: { property: entity, direction: ASC }
    subGroupBy: { property: group, direction: ASC }
    sort:
      - { property: date, direction: DESC }
    markers: number
  - type: record
    name: Categorise
    filters: 'group != "Transfers"'
    edit: true
    record:
      sections:
        - { name: Transaction, properties: [date, amount, entity, account, merchant, plaid] }
        - { name: Category, properties: [category, group, how] }
// transactions.json: 19 rows, 10 KB; the first 3
{
 "id": "money-transactions-illustrative",
 "title": "Transactions (illustrative: money's test fixture)",
 "titleProperty": "name",
 "source": "ILLUSTRATIVE: ~/money/tests/acceptance/fixture_home (synthetic), booked by money's src/money/ledger.py; no real account data",
 "properties": {
  "name": {
   "type": "text",
   "label": "Description",
   "readOnly": true
  },
  "date": {
   "type": "date",
   "label": "Date",
   "readOnly": true
  },
  "entity": {
   "type": "text",
   "label": "Entity",
   "display": "badge",
   "readOnly": true,
   "colors": {
    "bam": 1,
    "personal": 5
   }
  },
  "account": {
   "type": "text",
   "label": "Bank account",
   "readOnly": true
  },
  "merchant": {
   "type": "text",
   "label": "Merchant",
   "readOnly": true
  },
  "amount": {
   "type": "number",
   "label": "Amount",
   "unitText": "CAD",
   "decimals": 2,
   "readOnly": true,
   "note": "money in positive, money out negative"
  },
  "currency": {
   "type": "text",
   "label": "Currency",
   "hidden": true,
   "readOnly": true
  },
  "category": {
   "type": "text",
   "label": "Category",
   "constraints": {
    "enum": [
     "Equity:Transfers",
     "Expenses:Entertainment",
     "Expenses:Fees",
     "Expenses:Food",
     "Expenses:Government",
     "Expenses:Health",
     "Expenses:Housing",
     "Expenses:Loan-Payments",
     "Expenses:Other",
     "Expenses:Payroll",
     "Expenses:Purchases",
     "Expenses:Services",
     "Expenses:Transport",
     "Expenses:Travel",
     "Income:Interest",
     "Income:Investment",
     "Income:Other",
     "Income:Revenue",
     "Income:Salary"
    ]
   },
   "note": "the ledger account money books it to; fix one with a rules.toml rule"
  },
  "group": {
   "type": "text",
   "label": "Kind",
   "display": "badge",
   "derived": "the category's top level; a transfer between own accounts, or to Equity:Transfers, is Transfers",
   "colors": {
    "Expenses": 2,
    "Income": 3,
    "Transfers": 0
   }
  },
  "how": {
   "type": "text",
   "label": "Categorised by",
   "derived": "money's precedence: rule, matched transfer, Plaid category"
  },
  "plaid": {
   "type": "text",
   "label": "Plaid category",
   "readOnly": true
  },
  "transfer_pair": {
   "type": "boolean",
   "label": "Between own accounts",
   "hidden": true,
   "readOnly": true
  }
 },
 "rows": [
  {
   "id": "tx-p-10",
   "date": "2026-10-13",
   "entity": "personal",
   "account": "Assets:Cash:RBC-Royal-Bank:RBC-Day-to-Day-Banking-1234",
   "name": "E-TRANSFER TO MISA",
   "merchant": null,
   "amount": -200,
   "currency": "CAD",
   "category": "Equity:Transfers",
   "group": "Transfers",
   "how": "unmatched transfer",
   "plaid": "TRANSFER_OUT_ACCOUNT_TRANSFER",
   "transfer_pair": false
  },
  {
   "id": "tx-p-06",
   "date": "2026-10-12",
   "entity": "personal",
   "account": "Assets:Cash:RBC-Royal-Bank:RBC-Day-to-Day-Banking-1234",
   "name": "SAFEWAY #4402",
   "merchant": "Safeway",
   "amount": -85.2,
   "currency": "CAD",
   "category": "Expenses:Food",
   "group": "Expenses",
   "how": "Plaid FOOD_AND_DRINK",
   "plaid": "FOOD_AND_DRINK_GROCERIES",
   "transfer_pair": false
  },
  {
   "id": "tx-a-05",
   "date": "2026-10-11",
   "entity": "personal",
   "account": "Liabilities:CreditCard:American-Express:Amex-Cobalt-1005",
   "name": "CANADIAN TIRE #123",
   "merchant": "Canadian Tire",
   "amount": -120,
   "currency": "CAD",
   "category": "Expenses:Purchases",
   "group": "Expenses",
   "how": "Plaid GENERAL_MERCHANDISE",
   "plaid": "GENERAL_MERCHANDISE_OTHER_GENERAL_MERCHANDISE",
   "transfer_pair": false
  }
 ]
}
README.md
# Money transactions (illustrative)

Transactions grouped by kind and category, a table with a sum per category, the same rows by entity, and a form that
recategorises one. **Illustrative rows only:** money's synthetic test fixture, never real account data.

- **App:** Money
- **Replaces:** money has no transaction list of its own: its Flask pages (`statements.html` and `home.html` in
  `~/money`, `src/money/templates`) show totals by category, and the transactions themselves are only in Fava's journal.
  This is the list those totals come from, grouped the way the statements group them, with the category as a field
  you can propose a change to (money's own fix is a rule in `rules.toml`).
- **Shows:** `type: list` with `groupBy` + `subGroupBy` (kind -> category) and numbered markers, a table grouped by
  category with Sum summaries (view summaries, from the rows), and `type: record` with `edit: true`: the category is
  a select over money's account list (`constraints.enum`), everything the bank reported is read-only. On this static
  page the host refuses the save, and says so.
- **Data:** `transactions.json`, 19 rows written by `source/make.py` from `tests/acceptance/fixture_home` in `~/money`
  (synthetic: ACME WASTE, account masks 9001 and 1234), booked by money's `src/money/ledger.py` (rules first, then
  matched transfers, then Plaid's category). Amounts are CAD, money in positive.

The original UI it replaces

money has no transaction list of its own: its Flask pages (statements.html and home.html in ~/money, src/money/templates) show totals by category, and the transactions themselves are only in Fava's journal. This is the list those totals come from, grouped the way the statements group them, with the category as a field you can propose a change to (money's own fix is a rule in rules.toml).

What it shows

type: list with groupBy + subGroupBy (kind -> category) and numbered markers, a table grouped by category with Sum summaries (view summaries, from the rows), and type: record with edit: true: the category is a select over money's account list (constraints.enum), everything the bank reported is read-only. On this static page the host refuses the save, and says so.

Data

transactions.json, 19 rows written by source/make.py from tests/acceptance/fixture_home in ~/money (synthetic: ACME WASTE, account masks 9001 and 1234), booked by money's src/money/ledger.py (rules first, then matched transfers, then Plaid's category). Amounts are CAD, money in positive.