> For the complete documentation index, see [llms.txt](https://docs.holons.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.holons.io/software/expenses.md).

# Expenses

The **Expenses** domain in `@holons/core/expenses` tracks shared monetary expenses inside a holon: who paid, what for, in which currency, and how the cost should be split among participants. From those raw records it computes per-user balances and a credit matrix showing who owes what to whom.

Where [REA Accounting](/software/rea-accounting.md) is the event ledger for **all** value flows in a holon, Expenses is the specialized layer for **monetary** ones. Every expense action produces REA events that feed [Scoring](/software/scoring.md), but the Expenses domain owns its own data model because monetary balances need stronger guarantees (currency normalization, integer-safe arithmetic, multi-currency netting) than a generic event stream provides.

## Concepts

### Expense

```ts
interface Expense {
  id: AgentId;
  date: number;                // Unix epoch ms
  amount: number;
  currency: string;            // normalized: lowercase, singular, a–z only
  description: string;
  paidBy: AgentId;
  splitWith: AgentId[];
  picture?: string | null;     // optional Telegram file_id for a receipt
}

type AgentId = string | number;
```

A few notes on the shape:

* **IDs are loose.** Telegram stores numeric user IDs; the web app uses UUID strings. The domain accepts both and normalizes at the storage edge.
* **Currency is normalized.** "EUR", "euros", "Euro" all become `eur` via `normalizeCurrency()`. This is what makes multi-currency totals additive without per-call casing checks.
* **`splitWith` can be loose.** Older records may arrive as a non-array. `coerceSplitWith()` is the canonical normalizer.
* **Pictures are optional.** Receipts are stored as Telegram file IDs—external blob storage stays out of the domain.

### Balance

```ts
interface UserBalance {
  userId: AgentId;
  net: number;       // positive = is owed; negative = owes
}
```

A single number per user per currency. Positive means the holon owes them; negative means they owe the holon.

### Credit matrix

```ts
interface BalancesResult {
  creditMatrix: number[][];   // creditMatrix[i][j] = what i is owed by j
  userIds: AgentId[];
  balances: UserBalance[];
}
```

An NxN matrix where `[i][j]` is the net amount user `i` is owed by user `j`. Row sums give per-user net balances; column sums give "how much does each user owe in total."

This is what lets the bot answer "settle up with Laura" by reading one cell of the matrix rather than reconstructing the full debt graph each time.

## Operations

### Creation and editing

| Function                             | Purpose                                      |
| ------------------------------------ | -------------------------------------------- |
| `createExpense(input)`               | Build an Expense from a `CreateExpenseInput` |
| `addParticipant(expense, userId)`    | Add a user to `splitWith` (pure)             |
| `removeParticipant(expense, userId)` | Remove (pure)                                |
| `toggleParticipant(expense, userId)` | Convenience toggle (pure)                    |
| `splitAmongAll(expense, allUsers)`   | Spread the cost across every user in a holon |

Operations are pure—they return a new `Expense`. Persistence is done by the calling UI through [HoloSphere](/software/holosphere.md).

### Balance computation

| Function                                                 | Purpose                                        |
| -------------------------------------------------------- | ---------------------------------------------- |
| `computeBalances(expenses, users)`                       | Full `BalancesResult` — matrix + per-user nets |
| `computeCreditMatrix(expenses, userIds)`                 | Just the matrix                                |
| `computeUserCurrencyBalance(expenses, userId, currency)` | One user, one currency                         |
| `coerceSplitWith(value)`                                 | Defensive normalizer for legacy data           |
| `normalizeCurrency(currency)`                            | Canonical currency code                        |

Aliases for legacy call sites:

* `calculateBalance` = `computeUserCurrencyBalance`
* `calculateCreditMatrix` = `computeCreditMatrix`

All computation is pure. A web UI rendering "you owe Laura €12.50" runs the same function as the Telegram bot running `/balance`.

## How a split works

The conventional even-split:

```
Each participant owes: amount / splitWith.length
The payer is credited:  amount
The payer's net:        +amount - (amount / splitWith.length)  if they're in splitWith
                        +amount                                  if they're not
```

For multi-currency holons, the same logic runs independently per currency. There is no implicit exchange rate—balances stay in the currency they were incurred in, and users can clear them in whichever currency makes sense.

## Integration with REA

When an expense is created, the corresponding [REAEventFactory](/software/rea-accounting.md#the-event-factory) emits:

* one `expense:paid` event with the payer as provider,
* one share event per participant, with the participant as receiver of the cost obligation.

These events feed scoring via the standard aggregator pipeline. A holon that values "covering shared costs" can give weight to `expense:paid` events in its [value equation](/software/scoring.md), so contributing financially becomes a recognized form of participation alongside hours and appreciations.

## Storage layout

Expenses are persisted in the `expenses` lens of their holon. Each expense is keyed by its `id` (Telegram message ID or UUID); reads use the standard `getAll` pattern.

The Telegram bot's persistence shape is the authoritative one — both the web app and any future UI normalize to it on write. This is what lets the same holon's expenses be edited from any interface without per-UI translation.

## MCP tool surface

The [MCP server](/software/mcp-server.md) exposes **6 tools** in the `expenses` domain:

* expense CRUD (`expense_create`, `expense_get`, `expense_delete`)
* split helpers (`split_expense`)
* balance computation (per-user, full matrix)

An MCP-connected agent can log a shared meal, calculate who owes whom, or settle a balance entirely through tool calls.

## See also

* [REA Accounting](/software/rea-accounting.md) — the event ledger expenses emit into
* [Scoring](/software/scoring.md) — how monetary contributions become recognized points
* [Library](/software/library.md) — sibling resource-sharing domain (with deposit accounting)
* [MCP Server](/software/mcp-server.md) — the tool surface


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.holons.io/software/expenses.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
