An AI request log often outlives the code that parsed its response. If an adapter substitutes zero whenever usage metadata is unavailable, a dashboard can incorrectly report a free or tokenless request. Preserve whether a count is known, explicitly unknown or absent before computing aggregates. This PHP example makes those states visible in a small normalized usage contract.
Prerequisites and input contract
Use a maintained PHP 8 release with the CLI and JSON extension. The executed version was PHP 8.3.6. No SDK, API key or service is required. The JSON fixture is application-owned: input_tokens and output_tokens are normalized field names, not a promise that all providers expose those exact fields. A provider-specific adapter must map its documented final usage metadata into this contract.
For each field, an absent key produces state missing; an explicit null produces unknown; a nonnegative PHP integer produces known with a value. Strings, booleans, fractions and negative numbers fail. Checking key presence before checking null is essential to this policy. A known count of zero stays measurable zero instead of being mistaken for missing data.
Complete example
Save as usage.php.
<?php
declare(strict_types=1);
function readUsage(array $record): array {
$result = [];
foreach (['input_tokens', 'output_tokens'] as $key) {
if (!array_key_exists($key, $record)) {
$result[$key] = ['state' => 'missing'];
} elseif ($record[$key] === null) {
$result[$key] = ['state' => 'unknown'];
} elseif (!is_int($record[$key]) || $record[$key] < 0) {
throw new InvalidArgumentException('invalid token count');
} else {
$result[$key] = ['state' => 'known', 'value' => $record[$key]];
}
}
return $result;
}
function check(bool $ok): void {
if (!$ok) throw new RuntimeException('check failed');
}
// Application-normalized fixture, not a provider response schema.
$record = json_decode('{"input_tokens":0,"output_tokens":null}', true, 16, JSON_THROW_ON_ERROR);
$usage = readUsage($record);
check($usage['input_tokens'] === ['state' => 'known', 'value' => 0]);
check($usage['output_tokens'] === ['state' => 'unknown']);
check(readUsage([])['input_tokens']['state'] === 'missing');
$rejected = 0;
foreach ([-1, 1.5, '12', true] as $bad) {
try { readUsage(['input_tokens' => $bad]); }
catch (InvalidArgumentException $e) { $rejected++; }
}
check($rejected === 4);
echo "input=known:0 output=unknown absent=missing rejected=4\n";
Run and expected output
php usage.phpinput=known:0 output=unknown absent=missing rejected=4
Use the states in reporting
The fixture records zero input tokens and unknown output tokens. A second check verifies absent fields. The example uses an explicit check function, so its validation does not depend on whether PHP assertions are enabled. Four malformed count types must raise an exception. JSON parsing uses an exception on invalid JSON rather than continuing with an ambiguous decoding failure.
A reporting layer can sum known values while separately counting requests with unavailable measurements. Label that sum as measured usage and publish its coverage; do not imply that an incomplete sum represents every request. A known input count does not make an unknown output count known. This boundary intentionally does not invent a total, estimate billing or apply model prices.
Edge cases and troubleshooting
If a provider emits numeric strings, make any conversion an explicit adapter decision backed by its contract. Avoid an unconditional integer cast, which can conceal fractional values or turn malformed text into a number. Extremely large JSON numbers may not arrive as PHP integers; they are rejected here. During streaming, usage may be absent from intermediate events, so normalize a finalized usage record rather than overwriting a known measurement with each partial event. This tutorial tested the local normalization rules and JSON fixture only. It did not contact a model, parse a provider stream, verify a billing statement or exercise retry deduplication. Store a request identifier upstream if aggregates need to avoid counting the same completed call twice.
Primary references
Continue with PHP tutorials and AI tutorials.