StabisoDocs

Accounting and finance

Analytics

The Analytics section (Analytics in the navigation, also under Tell me) shows how a company is doing: ten key figures against a comparison, four trends, four breakdowns and what moved. Every number is computed on the server from the posted ledger (or, for the aging and the customer and vendor breakdowns, from the posted documents), by report_line; the browser only draws and formats what the API returns. The charts are plain SVG (ADR-0022).

REST first: the screen calls four documented operations, each also an MCP tool, and nothing else.

Operation MCP tool Gives
GET /v1/entities/{entity}/analytics/series analytics_series One metric per month, quarter or year
GET /v1/entities/{entity}/analytics/kpis analytics_kpis The ten KPIs with comparison and trend
GET /v1/entities/{entity}/analytics/breakdowns analytics_breakdown Top customers, vendors, sales currencies, the expense mix
GET /v1/entities/{entity}/analytics/insights analytics_insights Flux and unusual entries for any range, with a sentence per flagged account

/analytics/flux and /analytics/unusual-entries (a fiscal period, for the close) are the same rules for a period; insights generalises them to any range.

Metrics

The set is closed: metric takes one of these values and nothing else (no free-form queries). Amounts are in the entity's functional currency. A flow is what moved in the period, a stock is the balance at the period's end, a ratio is computed from the period's own figures, an aging is the open documents as of the period's end. Sign rules: a flow is added up in its natural direction (revenue credit less debit, expenses debit less credit), a stock in the account's normal direction (assets debit less credit, liabilities credit less debit), exactly as the trial balance and balance sheet do. Good news says which direction of change is good (favourable in the KPI answer); Split whether by=report_line|account is allowed.

Metric Kind Unit Definition Good news Split
revenue flow amount Credit less debit of the accounts of type revenue: the P&L's total revenue. up yes
expenses flow amount Debit less credit of the accounts of type expense: the P&L's total expenses. down yes
net_income flow amount Revenue less expenses: the P&L's net income. up no
cost_of_sales flow amount Debit less credit of the report line pl.cost_of_sales. down yes
operating_expenses flow amount Debit less credit of the report lines pl.opex.* and pl.depreciation. down yes
gross_margin_pct ratio percent (Revenue on pl.revenue less cost of sales) / revenue on pl.revenue x 100; empty when there is no such revenue. up no
cash stock amount Balance at the period's end of the report lines starting bs.assets.current.cash (the Cash figure of the Role Center). up yes
receivables stock amount Balance at the period's end of the report line bs.assets.current.receivables (the receivables control account in the ledger). neither yes
payables stock amount Balance at the period's end of the report line bs.liabilities.current.payables (the payables control account in the ledger). neither yes
unearned_revenue stock amount Balance at the period's end of the report line bs.liabilities.current.contract_liabilities: what customers paid or were billed for goods and services not yet delivered (contract liabilities, deferred revenue). neither yes
working_capital stock amount Report lines bs.assets.current.* less bs.liabilities.current.* at the period's end. up no
dso ratio days Open receivables without VAT at the period's end (from the documents: the carrying amount of each open customer invoice and credit note times its net total over its total) / revenue of the period x days in the period. down no
dpo ratio days Open payables without VAT at the period's end (from the vendor bills, as the receivables for DSO) / purchases of the period x days in the period; purchases are cost of sales plus the operating expenses that are bought on account (pl.opex.* except pl.opex.personnel). Capital purchases on account are not in purchases. neither no
receivables_aging aging amount Open customer invoices less credit notes as of the period's end, in the functional currency at the booking rate, in the buckets of the aging report (current, 1-30, 31-60, 61-90, 90+ days past due). Equals totals_functional of GET /v1/entities/{entity}/reports/aging as of that day. down no
payables_aging aging amount Open vendor bills less credit notes as of the period's end, as the receivables aging. neither no

Ratios have one decimal, rounded half up, and no value (null) where the denominator is zero. The report lines are those of the chart of accounts the entity uses (pl.revenue, pl.cost_of_sales, pl.opex.*, pl.depreciation, bs.assets.current.*, bs.liabilities.current.*). The KPI Revenue is the P&L's total revenue, as on the Role Center, while the gross margin uses only the revenue on pl.revenue (not interest or other income). DSO and DPO use the length of the range in days and compare net with net: the numerator is what is open without VAT, read from the documents (each open invoice, bill or credit note's carrying amount times its net total over its total), the denominator net revenue or net purchases. DPO's purchases are cost of sales and the operating expenses bought on account, so capital purchases on account (a bill booked to a fixed asset) are not in them; while such a bill is open it is in the numerator, which overstates DPO.

Periods and comparison

  • Periods are calendar months, quarters or years (granularity). The first and last period of a range are clamped to it, so the sum of a flow over the periods is the report's total for the range, and the last value of a stock is the trial balance as of end. More than 60 periods is refused (422): ask for a coarser granularity.
  • Comparison (compare): previous is the same number of whole months (or of days, when the range is not whole months) right before the range; year_ago is the same dates one year earlier (29 February becomes 28 February, a month's last day stays its last day). The answer states the comparison range it used.
  • A delta is the value less the comparison (points for a percentage, days for days); delta_pct is the delta as a percentage of the absolute comparison, for amounts only.
  • Calendar periods. Months, quarters and years are calendar ones, also for an entity with a 4-4-5 or 52/53-week fiscal year, whose periods do not coincide with them; the fiscal year to date starts where the entity's fiscal year starts, and a fiscal period's own figures are the period profit and loss (reports/period-profit-and-loss).
  • The screen's selector offers a month, a quarter, the fiscal year to date and a custom range; it chooses the dates, the server does the arithmetic.

What ties to what

Each tie is a test (tests/erp/features/analytics/test_analytics.py) that reads the analytics endpoint and the existing report for the same dates:

Analytics Equals
revenue, expenses, net_income per month; their sum over the range profit-and-loss revenue, expenses, net_income for the month; for the range
cost_of_sales, operating_expenses the sections of the profit and loss on those report lines
cash, receivables, payables at a month end trial balance as of that day, the accounts on those report lines
unearned_revenue at a month end balance sheet, the contract liabilities line
working_capital at a month end balance sheet current assets less current liabilities
receivables_aging, payables_aging at a month end totals_functional of GET .../reports/aging as of that day
a KPI's value, comparison and spark the report for the same dates; the series of the same range
the expense mix the expense sections of the profit and loss
a customer, vendor or currency row's documents the count of the document list filtered by the same party or currency, dates and status=posted

Aging as of a day. A payment or credit note made after the day has not settled anything yet: the aging report and the aging trend count a settlement from the day of the payment or of the credit note. "Open as of a day" has one definition (Position in modules/trade/open_items.py), also read by the FX revaluation and the exposure view:

  • the open amount in the document currency reserves pending allocations (a payment waiting for approval), so an item is not offered for payment twice;
  • the functional amount (open_functional, totals_functional) is the carrying amount the ledger holds: the booked total less the carrying amounts of the posted allocations, each rounded as it was booked. It ties to the revaluation, and to the control account until a revaluation moves it.

Reserved documents. A document that pending allocations reserve in full is listed all the same, flagged reserved (open 0 in the document currency, its carrying amount in open_functional, its due-date bucket), so the functional totals are complete and the document-currency totals do not change.

The aging ties to the control account. The report carries a reconciliation as of its day: the ledger balance of the receivables or payables control account (control_balance), the aging's totals_functional, their difference, and the part of it that is unrealised_fx: the period-end revaluation postings (source fx) that sit on the control account, on no document, until their auto-reversal. unexplained (the difference less the revaluation) is zero when the books are in order. pending_settlements is a memo: payments waiting for approval are not booked, so they are in neither control_balance nor totals_functional and explain no part of the difference. The screen shows the line under the open items and under the aging chart.

Year-end closing entries. Profit-and-loss flows leave out entries of source close (the year-end closing entry of ADR-0023), so the last month of a closed year keeps its revenue and result. Balances include every entry, and an appropriation entry is read like any other. The rule sits in one place (CLOSING_SOURCES in modules/closing_entries.py, shared with the reports); a year closed with the year-end close keeps its income statement, and a trend that ends in the closing month shows what the month earned.

Breakdowns

customers, vendors and currency are read from posted documents by document date, invoices less credit notes, at the document's net total in the functional currency (proportional to the booked total; a document in the functional currency is its net total exactly). Manual revenue entries without an invoice are therefore not in them; the trend and the P&L have them. expense_mix is read from the ledger by report line. Rows are largest first, at most limit (default 10), the rest as one row other.

Insights

insights flags each account whose profit-and-loss movement over the range (or balance-sheet balance at its end) differs from the comparison range by at least the materiality (default 1,000 in the functional currency) and 10%. Each flagged account has change (increase, decrease, new, cleared) and one English sentence built from its numbers by a fixed template, with no language model, so the same books always give the same words. The web UI writes the same sentence from the numbers in the user's language. Unusual entries are the entries with a risk flag (manual, created by an agent, round amount, large amount, weekend date, reversal), largest first.

Performance

A series or the KPI strip reads the posted lines of the range with one aggregate query (a group per account and period) and the balances before it with one more; the aging reads the open documents once. The ledger query uses the partial index journal_entry_date_idx (entity, entry date, posted entries); tests/erp/features/analytics/test_analytics.py reads its plan. Migration 0024 adds a partial index on allocation (credit_note_id), for the credit-note side of settlements.

Stabiso, the Enterprise System of Record · generated from the code and its guides · release 20b7dd5b0c4d