feat: AI ticket analyzer (phases 1-6)
Multi-stage LLM pipeline that produces structured analyses of Autotask tickets from local Postgres. Migration 069 + Zod schemas, Stage 0 preprocessor, IT Glue redaction + search, Anthropic SDK wrapper, Stages 1/3/4 (Haiku/Sonnet/Opus), pipeline + cost circuit breaker, job worker (opt-in autostart), 6 API routes, 3 frontend pages, share-row persistence (email send deferred to phase 7). 128 vitest tests, tsc clean. Build journal in docs/wulf-pulse-ticket-analyzer-build-notes.md. Sync: adds syncTicketNotes() + ticket_notes to ordered/date-filtered entities so the analyzer's local mirror stays current via scheduler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ea3471d38d
commit
8f8b5ab7be
53 changed files with 9377 additions and 33 deletions
52
lib/services/llm/pricing.ts
Normal file
52
lib/services/llm/pricing.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Per-model token pricing for cost estimation in analyzer_analyses.estimated_cost_usd.
|
||||
*
|
||||
* Rates are USD per 1,000,000 tokens.
|
||||
*
|
||||
* VERIFY QUARTERLY against https://docs.claude.com/en/docs/about-claude/pricing
|
||||
* Last verified: 2026-04-15
|
||||
*/
|
||||
|
||||
import { HAIKU, SONNET, OPUS, type ModelId } from './models';
|
||||
|
||||
interface ModelRate {
|
||||
/** USD per 1M input tokens */
|
||||
input: number;
|
||||
/** USD per 1M output tokens */
|
||||
output: number;
|
||||
/** USD per 1M tokens read from prompt cache (~0.1× input) */
|
||||
cacheRead: number;
|
||||
/** USD per 1M tokens written to 5-minute prompt cache (~1.25× input) */
|
||||
cacheWrite5m: number;
|
||||
}
|
||||
|
||||
export const PRICING: Record<ModelId, ModelRate> = {
|
||||
[HAIKU]: { input: 1.0, output: 5.0, cacheRead: 0.1, cacheWrite5m: 1.25 },
|
||||
[SONNET]: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite5m: 3.75 },
|
||||
[OPUS]: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite5m: 6.25 },
|
||||
};
|
||||
|
||||
export interface TokenUsage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* USD cost of a single LLM call given its token usage. Caches are billed
|
||||
* separately from raw input tokens — cache_read at ~0.1×, cache_write at ~1.25×.
|
||||
*/
|
||||
export function estimateCostUsd(model: ModelId, usage: TokenUsage): number {
|
||||
const rate = PRICING[model];
|
||||
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
||||
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
||||
const uncachedInput = usage.input_tokens; // SDK reports this as the uncached remainder
|
||||
const cost =
|
||||
(uncachedInput / 1_000_000) * rate.input +
|
||||
(usage.output_tokens / 1_000_000) * rate.output +
|
||||
(cacheRead / 1_000_000) * rate.cacheRead +
|
||||
(cacheWrite / 1_000_000) * rate.cacheWrite5m;
|
||||
// Round to 4 decimals (matches analyzer_analyses.estimated_cost_usd numeric(10,4)).
|
||||
return Math.round(cost * 10_000) / 10_000;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue