An editor's budget status 

UserBudgetStatusService reads one backend user's own usage today and this month against the limits of their budget record, the numbers the AI Tasks module shows (ADR-232). It reads the same windows and the same record that BudgetServiceInterface::check() compares: today from 00:00, this month from the first at 00:00, both up to now.

use Netresearch\NrLlm\Service\Budget\UserBudgetStatusService;
use Netresearch\NrLlm\Service\Formatting\LocalizedNumberFormatter;

final readonly class MyBudgetStatusReader
{
    public function __construct(
        private UserBudgetStatusService $budgetStatus,
    ) {}

    /**
     * @param int $beUserUid the logged-in backend user, never a request value
     */
    public function read(int $beUserUid): array
    {
        return $this->budgetStatus
            ->forUser($beUserUid, LocalizedNumberFormatter::forBackendUser())
            ->toArray();
    }
}
Copied!

Obtain the service from the DI container; its constructor is not part of the promise. Pass the uid of the logged-in backend user. A uid of 0 or below reads no usage, because be_user = 0 holds the usage of every caller without a backend user (command line, scheduler).

UserBudgetStatus
$hasPersonalLimit is true only when enforcement applies the record: it exists, is active and sets at least one limit above 0. Otherwise every limit reads 0 and the usage is still filled in. $today and $month are BudgetWindowStatus objects.
BudgetWindowStatus
$requests, $tokens and $cost, one BudgetMeter each.
BudgetMeter
$used and $limit as numbers; a limit of 0 means no limit on this axis (isLimited() is false). getPercent() is the share of the limit used, 0 to 100, rounded down. getUsedFormatted() and getLimitFormatted() format with the formatter passed to forUser(): cost as USD with two decimals, counts without.

toArray() on each of them returns the formatted values, for a JSON answer: hasPersonalLimit, then today and month, each with requests, tokens and cost as {used: string, limit: string, limited: bool, percent: int}.

LocalizedNumberFormatter formats numbers and USD amounts in a locale through ICU. forBackendUser() takes the locale of the current backend user and falls back to en-US without one; `new LocalizedNumberFormatter('de-DE')` fixes one. Amounts stay in USD, the currency budgets and prices are stored in.