ADR-231: A monthly billing report with a markup
- Status
-
Accepted
- Date
-
2026-10-28
- Authors
-
Netresearch DTT GmbH
Context
An agency that runs the AI features for a customer bills them monthly. The Analytics module (Usage analytics) shows usage over a rolling range, not a calendar month, and has no export. The billing needs four figures per month: the estimated cost with the agency's markup, the editors who used the features, the editors who were allowed to, and the results split by kind. A split per site or tenant is also wanted; usage rows carry no site today, and recording one needs every calling extension to pass page context. That is a separate decision.
Decision
A second page of the Analytics module, billing, shows one calendar
month; export downloads the same month as CSV. Both reuse the
queries of the Analytics page through Usage
and refuse a non-administrator themselves, on top of the module's
access => admin.
- The month is the calendar month in the server timezone, from the
first second to the last second of its last day.
request_is the local midnight of the day a request ran, and the queries compare inclusively, so the first second of the next month would pull that day in. The default is the previous complete month; the running month and the twelve before it are offered.date - The markup is the extension setting
billing., a whole percentage from 0 to 1000, default 0. It is added once to the month total, in micro-dollars, rounded half up; stored costs never change. A value outside the range is clamped and a value that is not a whole number reads 0, and the page says so.markup Percent - Active editors are the backend users with at least one request in
the month, whatever their account state is today; the state is shown.
Requests without a backend user (command line, scheduler) are a separate
row, not an editor; so is the usage of core's command-line user
_cli_, under which console commands record usage since TYPO3 14.3.8. The page shows cost and total rounded to cents and the markup as their difference, so the displayed lines add up; the CSV keeps 6 decimals. - Entitled editors are the users who can log in now, hold the
tasks_grant (ADR-130) through a group or a subgroup, and have theuse nrllm_module (ADR-131) through the user record or a group. Groups are resolved by core'saitasks Groupfor both the grant and the module, with its restrictions. Administrators are not counted, their number is stated. Users and groups keep no history, so this is always today's state, labelled with today's date.Resolver:: find All Users In Groups () - Results by kind group requests, tokens and cost by service type, calling extension (ADR-178) and task.
- The CSV is one table with a
sectioncolumn, so a spreadsheet imports it in one step: RFC 4180, comma, CRLF, UTF-8 without a byte order mark, dot decimals, amounts with 6 decimals in USD named in the column header. Text cells that would start a formula get a leading'.
Consequences
- A site dimension is one more column in the "by kind" grouping and one more CSV column, once usage rows carry a site.
- "Entitled" covers nr-llm's own permission only. An extension that calls the AI services with its own checks is not covered, so an active editor need not be entitled.
- The figures are the estimates of ADR-029, not provider invoices.
Alternatives considered
- Markup per row in the export. Rounding per row makes the rows not add up to the total; the markup is a property of the invoice, so it sits on the total row only.
- Separate CSV files per section. A billing spreadsheet would need several imports; one table with a section column needs one.
- Entitlement by the grant alone. A user with the grant but without the module has no page to run a task from, so counting them would overstate what the customer pays for.