How TREK Handles Budget Tracking and Multi-Currency: A Technical Deep Dive
TREK implements a dedicated Budget add-on that stores every expense with its own ISO-4217 currency code and an exchange_rate field, converting all amounts to the trip's base currency using real-time rates from exchangerate.host while broadcasting updates via WebSocket for collaborative synchronization.
TREK is an open-source trip planning platform that solves complex financial coordination through robust budget tracking and multi-currency support. Unlike simple expense splitters, TREK maintains full audit trails by preserving original currencies alongside conversion rates, ensuring every collaborator sees consistent, real-time financial data regardless of their local currency.
Data Schema Design for Multi-Currency Expenses
Budget Item Structure
Every expense in TREK is stored as a budget item defined in shared/src/budget/budget.schema.ts. This schema mandates three critical fields for multi-currency handling:
currency: An ISO-4217 code (nullable) representing the transaction's original currencytotal_price: The raw numeric amount in the original currencyexchange_rate: A numeric factor converting the amount to the trip's base currency
This design ensures full auditability by preserving the original transaction data alongside the conversion metadata. The system never overwrites the original amount, allowing future recalculation if rates change or if users need to verify historical conversions.
Trip-Level Base Currency
The reference currency for all aggregations resides in shared/src/trip/trip.schema.ts. Each trip record specifies a base currency (e.g., EUR) that serves as the canonical unit for all financial summaries, MCP tool responses, and UI displays. This standardization prevents currency confusion when collaborators from different countries view shared expenses.
Server-Side Conversion and Persistence Logic
Automatic Exchange Rate Resolution
TREK sources live exchange rates through server/src/services/currencyService.ts, a lightweight wrapper around the public exchangerate.host API. This service requires no API key and caches rates for the duration of the request to minimize external calls.
When currencyService.getRate(itemCurrency, baseCurrency) is invoked, it returns the current conversion factor, which the system then stores permanently with the budget item. This snapshot approach locks the rate at the time of transaction, preventing historical amounts from fluctuating with market changes.
Budget Service Operations
The server/src/services/budgetService.ts handles the complete lifecycle of budget items with specific multi-currency logic:
- Default fallback: If the client omits a currency, the service substitutes the trip's base currency
- Rate fetching: Calls
currencyService.getRate()to retrieve the conversion factor - Persistence: Stores the
exchange_ratealongside the originaltotal_priceandcurrency - Broadcast: Emits a
budget:updatedWebSocket event to synchronize all connected clients
This sequence ensures that every write operation applies consistent conversion logic before persisting to the database.
// Conceptual flow from budgetService.ts
async function createBudgetItem(data, tripBaseCurrency) {
const currency = data.currency || tripBaseCurrency;
const rate = await currencyService.getRate(currency, tripBaseCurrency);
return await db.budgetItems.create({
...data,
currency,
exchange_rate: rate,
total_price: data.amount
});
}
Real-Time UI and Collaborative Aggregation
Per-Item Currency Selection
The client/src/components/budget/BudgetForm.vue component provides a currency selector that defaults to the trip's base currency but allows users to specify any ISO-4217 code. This flexibility accommodates scenarios where travelers pay for accommodations in USD while their trip is budgeted in EUR.
When a user selects a non-base currency, the form submits both the raw amount and the chosen currency code, triggering the server-side conversion pipeline described above.
Unified Financial Reporting
All aggregated views in client/src/components/budget/BudgetSummary.vue compute totals using the stored conversion rates:
- Total trip cost: Sum of
(total_price × exchange_rate)for all items - Per-category breakdown: Grouped sums using the same conversion formula
- Per-person splits: Distributed costs calculated after currency normalization
This approach guarantees that every collaborator sees identical figures regardless of who added the expense or which currency was used for the original transaction.
AI Integration via MCP Tools
TREK exposes budget data through Model Context Protocol (MCP) tools defined in MCP.md. The budget-overview and budget-per-person tools return pre-converted amounts already normalized to the trip base currency, enabling AI assistants and third-party integrations to consume financial data without implementing their own conversion logic.
Access to these tools is governed by permission scopes defined in wiki/MCP-Scopes.md. Only users with budget:write scope or admins possessing budget_edit can modify items, ensuring that currency conversion and persistence occur only through authorized channels.
Summary
- TREK stores original currencies and exchange rates in
shared/src/budget/budget.schema.tsfor full auditability. - The
server/src/services/currencyService.tsfetches live rates from exchangerate.host without requiring API keys. - All aggregations use the stored
exchange_rateto converttotal_priceinto the trip base currency defined inshared/src/trip/trip.schema.ts. - Real-time synchronization occurs via WebSocket events (
budget:updated) broadcast fromserver/src/services/budgetService.ts. - MCP tools provide AI-ready financial data already normalized to the base currency.
Frequently Asked Questions
How does TREK handle currency conversion for budget items?
When a budget item is created, budgetService.createBudgetItem() calls currencyService.getRate() to fetch the current exchange rate from exchangerate.host between the item's currency and the trip's base currency. This rate is stored in the exchange_rate field alongside the original total_price, allowing the system to calculate converted values on-demand while preserving the original transaction data.
What happens if a user doesn't specify a currency for an expense?
The system defaults to the trip's base currency as defined in shared/src/trip/trip.schema.ts. The budget service detects the missing currency value and falls back to this default before processing, ensuring every item has a valid ISO-4217 code without requiring manual entry for local expenses.
Can multiple users see budget totals in their own currencies simultaneously?
No, TREK standardizes all displays to the trip's base currency. While each budget item stores its original currency, all aggregated views in BudgetSummary.vue and MCP tools convert amounts using the stored exchange_rate before display. This ensures all collaborators see identical financial figures during real-time WebSocket synchronization via the budget:updated event.
Which permissions are required to modify budget items?
Users need the budget:write scope to create or update items, while administrators with budget_edit can override existing entries. These permissions are enforced in server/src/services/budgetService.ts to ensure that only authorized actors can trigger currency conversion and persistence logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →