estimateNeurons exists four times and has already drifted
src/services/ai-review.ts exports the canonical implementation:
export function estimateNeurons(promptChars: number, maxOutputTokens: number, calls: number): number {
const inputTokens = Math.ceil(promptChars / 4);
return Math.max(1, Math.ceil((inputTokens + maxOutputTokens) * 0.035) * Math.max(1, calls));
}
Three other services define a private copy that drops the calls multiplier:
// ai-chat-qa.ts, ai-summaries.ts, ai-intent-router.ts — identical to each other, all private
function estimateNeurons(prompt: string, maxOutputTokens: number): number {
const inputTokens = Math.ceil(prompt.length / 4);
return Math.max(1, Math.ceil((inputTokens + maxOutputTokens) * 0.035));
}
Same formula, different arity, and the exported one is right there to import.
This under-reports real spend
ai-chat-qa.ts:158 retries the provider call:
for (let attempt = 0; attempt < 2 && !rawText; attempt += 1) {
Two provider calls, one call's worth of neurons recorded. The figure lands in ai_usage_events.estimated_neurons, which is what cost tracking reads and what the daily neuron-budget backstop (#budget-no-starve) compares against — so a runaway-loop guard is reading a number that is too small by exactly the factor that would indicate a runaway loop.
ai-summaries.ts and ai-intent-router.ts each call theirs from more than one site as well.
Fix
Delete the three private copies and import the exported one, passing the actual call count. The signatures differ (prompt: string vs promptChars: number), so each call site becomes estimateNeurons(text.length, maxTokens, calls).
Why it is worth doing beyond the arithmetic
extractAiText is duplicated across the same three files too. This is a cluster of copy-paste from one service to the next, and the drift is already visible: the version that got a needed third parameter is the one that stayed put, while the copies did not follow. Whichever service is written next will copy whichever neighbour is nearest.
Found during the maintainability audit; see the umbrella issue for the wider duplication survey.
estimateNeuronsexists four times and has already driftedsrc/services/ai-review.tsexports the canonical implementation:Three other services define a private copy that drops the
callsmultiplier:Same formula, different arity, and the exported one is right there to import.
This under-reports real spend
ai-chat-qa.ts:158retries the provider call:Two provider calls, one call's worth of neurons recorded. The figure lands in
ai_usage_events.estimated_neurons, which is what cost tracking reads and what the daily neuron-budget backstop (#budget-no-starve) compares against — so a runaway-loop guard is reading a number that is too small by exactly the factor that would indicate a runaway loop.ai-summaries.tsandai-intent-router.tseach call theirs from more than one site as well.Fix
Delete the three private copies and import the exported one, passing the actual call count. The signatures differ (
prompt: stringvspromptChars: number), so each call site becomesestimateNeurons(text.length, maxTokens, calls).Why it is worth doing beyond the arithmetic
extractAiTextis duplicated across the same three files too. This is a cluster of copy-paste from one service to the next, and the drift is already visible: the version that got a needed third parameter is the one that stayed put, while the copies did not follow. Whichever service is written next will copy whichever neighbour is nearest.Found during the maintainability audit; see the umbrella issue for the wider duplication survey.