Data model

All types live in src/types/ and are re-exported from @/types.

Entities (entities.ts)

Type Notes
Company, Branch, Department, Region, SalesChannel, Product Dimensions used for filtering and breakdowns
Account code, name, type (asset/liability/equity/revenue/expense), optional subtype (bank, cogs, salaries…) used by statement grouping
Customer segment, region, status (active/inactive/at_risk)
Vendor category, paymentTermsDays
Transaction One ledger line. Lines belonging to the same business event share a reference. Exactly one of debit/credit is non-zero
Invoice, Bill amount, paid, dueDate, status
Payment Links to an invoice or bill
InventoryItem quantity, unitCost, unitsSold12m, lastMovementDate

Money is a number in the reporting currency. There is no minor-unit convention — send decimals.

Filters (filters.ts)

GlobalFilters carries the date preset, resolved range, comparison mode and up to eight dimension filters (companyId, branchId, departmentId, customerId, vendorId, accountId, currency, categoryId).

FilterParams extends it with granularity, search, page, pageSize, sortBy, sortDir — this is what every adapter method receives.

Comparison modes: previous_period, previous_year, budget, forecast.

Metrics (metrics.ts)

Type Shape
ComparedValue { current, previous, change, changePercent }
KpiMetric A ComparedValue plus key, label, description, format, sparkline, higherIsBetter
TimeSeriesPoint { date, label, value, previous?, budget? }
BreakdownItem { id, name, value, previous?, share, meta? } — meta carries the dimension values a drill-down should apply
ReportLine Recursive statement line with level, isTotal, accountId, children
AgingRow One open document with daysOverdue and bucket
Paginated<T> { items, total, page, pageSize }

Calculation semantics

Implemented in src/lib/calculations/financial.ts, all pure and null-safe.

  • Percentages are whole numbers. 14.8 means 14.8%. Keep this convention or formatting will be off by two orders of magnitude.
  • Impossible ratios return null, never NaN or Infinity. Growth from a zero base is null; the UI renders it as "n/a".
  • Growth uses the absolute base, so improving from −100 to −50 reads as +50% rather than −50%.
  • Variance is actual minus budget. Positive means above plan, which is good for revenue and bad for expenses — hence higherIsBetter on BudgetLine.
  • Ageing buckets: current, 0-30, 31-60, 61-90, 91-120, 120+. Days overdue never goes negative.
  • Forecasting is least-squares regression over the trailing series with a band that widens by 15% per step out. Replace it by returning your own ForecastPoint[] from /budget.

The demo dataset

src/mock/dataset.ts generates a deterministic 25-month double-entry ledger from a seeded PRNG — the same numbers on every machine and every reload.

  • ~8,900 ledger lines across 31 accounts (scalable to 250,000 — see docs/PERFORMANCE.md)
  • 24 customers, 17 vendors, 12 products, 5 branches, 6 departments
  • 1,100+ invoices and 350+ bills, with realistic partial payments and overdue tails
  • Payroll, depreciation, interest, loan repayments, capex and credit-line draws
  • Seasonality (Q4 uplift), a growth trend, and deliberately planted expense anomalies for the detection panel
  • Budgets for the last two fiscal years

Invariants the test suite enforces: the trial balance balances, the balance sheet reconciles, P&L net profit matches the profitability engine, cash flow closes to opening plus net movement, and receivables split cleanly into current and overdue.

src/mock/engine.ts computes every report from that ledger — it is a working reference for what your backend needs to produce.