llm_cost_tracker 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +16 -9
  4. data/app/models/llm_cost_tracker/ledger/call.rb +1 -1
  5. data/app/models/llm_cost_tracker/ledger/call_metrics.rb +1 -1
  6. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +9 -9
  7. data/lib/llm_cost_tracker/capture/stream_collector.rb +11 -4
  8. data/lib/llm_cost_tracker/capture/stream_tracker.rb +1 -1
  9. data/lib/llm_cost_tracker/configuration.rb +5 -1
  10. data/lib/llm_cost_tracker/integrations/anthropic.rb +25 -8
  11. data/lib/llm_cost_tracker/integrations/openai.rb +4 -4
  12. data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +4 -10
  13. data/lib/llm_cost_tracker/ledger/rollups.rb +7 -7
  14. data/lib/llm_cost_tracker/ledger/store.rb +22 -13
  15. data/lib/llm_cost_tracker/ledger/tags/query.rb +5 -5
  16. data/lib/llm_cost_tracker/ledger/tags/sql.rb +8 -7
  17. data/lib/llm_cost_tracker/middleware/faraday.rb +56 -13
  18. data/lib/llm_cost_tracker/parsers/anthropic.rb +35 -13
  19. data/lib/llm_cost_tracker/parsers/base.rb +2 -2
  20. data/lib/llm_cost_tracker/parsers/gemini.rb +39 -13
  21. data/lib/llm_cost_tracker/parsers/openai.rb +27 -5
  22. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +14 -4
  23. data/lib/llm_cost_tracker/parsers/openai_usage.rb +41 -13
  24. data/lib/llm_cost_tracker/prices.json +316 -32
  25. data/lib/llm_cost_tracker/pricing/effective_prices.rb +23 -17
  26. data/lib/llm_cost_tracker/pricing/explainer.rb +17 -11
  27. data/lib/llm_cost_tracker/pricing/lookup.rb +44 -22
  28. data/lib/llm_cost_tracker/pricing/sync.rb +19 -3
  29. data/lib/llm_cost_tracker/tracker.rb +6 -4
  30. data/lib/llm_cost_tracker/version.rb +1 -1
  31. metadata +2 -2
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "metadata": {
3
- "updated_at": "2026-04-27",
3
+ "updated_at": "2026-05-01",
4
4
  "currency": "USD",
5
5
  "unit": "1M tokens",
6
6
  "source_urls": [
7
7
  "https://developers.openai.com/api/docs/pricing",
8
8
  "https://platform.claude.com/docs/en/about-claude/pricing",
9
- "https://ai.google.dev/pricing"
9
+ "https://ai.google.dev/gemini-api/docs/pricing",
10
+ "https://console.groq.com/docs/models",
11
+ "https://console.groq.com/docs/prompt-caching",
12
+ "https://console.groq.com/docs/flex-processing",
13
+ "https://console.groq.com/docs/service-tiers"
10
14
  ],
11
15
  "schema_version": 1,
12
16
  "min_gem_version": "0.4.0"
@@ -55,7 +59,24 @@
55
59
  "cache_write_input": 6.25,
56
60
  "cache_write_1h_input": 10.0,
57
61
  "batch_input": 2.5,
58
- "batch_output": 12.5
62
+ "batch_output": 12.5,
63
+ "data_residency_input": 5.5,
64
+ "data_residency_cache_write_input": 6.875,
65
+ "data_residency_cache_write_1h_input": 11.0,
66
+ "data_residency_cache_read_input": 0.55,
67
+ "data_residency_output": 27.5,
68
+ "data_residency_batch_input": 2.75,
69
+ "data_residency_batch_output": 13.75,
70
+ "fast_input": 30.0,
71
+ "fast_cache_write_input": 37.5,
72
+ "fast_cache_write_1h_input": 60.0,
73
+ "fast_cache_read_input": 3.0,
74
+ "fast_output": 150.0,
75
+ "fast_data_residency_input": 33.0,
76
+ "fast_data_residency_cache_write_input": 41.25,
77
+ "fast_data_residency_cache_write_1h_input": 66.0,
78
+ "fast_data_residency_cache_read_input": 3.3,
79
+ "fast_data_residency_output": 165.0
59
80
  },
60
81
  "anthropic/claude-opus-4-7": {
61
82
  "input": 5.0,
@@ -64,7 +85,14 @@
64
85
  "cache_write_input": 6.25,
65
86
  "cache_write_1h_input": 10.0,
66
87
  "batch_input": 2.5,
67
- "batch_output": 12.5
88
+ "batch_output": 12.5,
89
+ "data_residency_input": 5.5,
90
+ "data_residency_cache_write_input": 6.875,
91
+ "data_residency_cache_write_1h_input": 11.0,
92
+ "data_residency_cache_read_input": 0.55,
93
+ "data_residency_output": 27.5,
94
+ "data_residency_batch_input": 2.75,
95
+ "data_residency_batch_output": 13.75
68
96
  },
69
97
  "anthropic/claude-sonnet-4": {
70
98
  "input": 3.0,
@@ -91,7 +119,14 @@
91
119
  "cache_write_input": 3.75,
92
120
  "cache_write_1h_input": 6.0,
93
121
  "batch_input": 1.5,
94
- "batch_output": 7.5
122
+ "batch_output": 7.5,
123
+ "data_residency_input": 3.3,
124
+ "data_residency_cache_write_input": 4.125,
125
+ "data_residency_cache_write_1h_input": 6.6,
126
+ "data_residency_cache_read_input": 0.33,
127
+ "data_residency_output": 16.5,
128
+ "data_residency_batch_input": 1.65,
129
+ "data_residency_batch_output": 8.25
95
130
  },
96
131
  "gemini/gemini-2.0-flash": {
97
132
  "input": 0.1,
@@ -113,7 +148,13 @@
113
148
  "cache_read_input": 0.03,
114
149
  "batch_input": 0.15,
115
150
  "batch_output": 1.25,
116
- "batch_cache_read_input": 0.03
151
+ "batch_cache_read_input": 0.03,
152
+ "flex_input": 0.15,
153
+ "flex_output": 1.25,
154
+ "flex_cache_read_input": 0.03,
155
+ "priority_input": 0.54,
156
+ "priority_output": 4.5,
157
+ "priority_cache_read_input": 0.054
117
158
  },
118
159
  "gemini/gemini-2.5-flash-lite": {
119
160
  "input": 0.1,
@@ -121,7 +162,13 @@
121
162
  "cache_read_input": 0.01,
122
163
  "batch_input": 0.05,
123
164
  "batch_output": 0.2,
124
- "batch_cache_read_input": 0.01
165
+ "batch_cache_read_input": 0.01,
166
+ "flex_input": 0.05,
167
+ "flex_output": 0.2,
168
+ "flex_cache_read_input": 0.01,
169
+ "priority_input": 0.18,
170
+ "priority_output": 0.72,
171
+ "priority_cache_read_input": 0.018
125
172
  },
126
173
  "gemini/gemini-2.5-pro": {
127
174
  "input": 1.25,
@@ -136,7 +183,57 @@
136
183
  "above_context_cache_read_input": 0.25,
137
184
  "above_context_batch_input": 1.25,
138
185
  "above_context_batch_output": 7.5,
139
- "above_context_batch_cache_read_input": 0.25
186
+ "above_context_batch_cache_read_input": 0.25,
187
+ "flex_input": 0.625,
188
+ "flex_output": 5.0,
189
+ "above_context_flex_input": 1.25,
190
+ "above_context_flex_output": 7.5,
191
+ "flex_cache_read_input": 0.125,
192
+ "above_context_flex_cache_read_input": 0.25,
193
+ "priority_input": 2.25,
194
+ "priority_output": 18.0,
195
+ "above_context_priority_input": 4.5,
196
+ "above_context_priority_output": 27.0,
197
+ "priority_cache_read_input": 0.225,
198
+ "above_context_priority_cache_read_input": 0.45
199
+ },
200
+ "groq/llama-3.1-8b-instant": {
201
+ "input": 0.05,
202
+ "output": 0.08,
203
+ "on_demand_input": 0.05,
204
+ "on_demand_output": 0.08,
205
+ "flex_input": 0.05,
206
+ "flex_output": 0.08
207
+ },
208
+ "groq/llama-3.3-70b-versatile": {
209
+ "input": 0.59,
210
+ "output": 0.79,
211
+ "on_demand_input": 0.59,
212
+ "on_demand_output": 0.79,
213
+ "flex_input": 0.59,
214
+ "flex_output": 0.79
215
+ },
216
+ "groq/openai/gpt-oss-120b": {
217
+ "input": 0.15,
218
+ "cache_read_input": 0.075,
219
+ "output": 0.6,
220
+ "on_demand_input": 0.15,
221
+ "on_demand_cache_read_input": 0.075,
222
+ "on_demand_output": 0.6,
223
+ "flex_input": 0.15,
224
+ "flex_cache_read_input": 0.075,
225
+ "flex_output": 0.6
226
+ },
227
+ "groq/openai/gpt-oss-20b": {
228
+ "input": 0.075,
229
+ "cache_read_input": 0.0375,
230
+ "output": 0.3,
231
+ "on_demand_input": 0.075,
232
+ "on_demand_cache_read_input": 0.0375,
233
+ "on_demand_output": 0.3,
234
+ "flex_input": 0.075,
235
+ "flex_cache_read_input": 0.0375,
236
+ "flex_output": 0.3
140
237
  },
141
238
  "openai/gpt-3.5-turbo": {
142
239
  "input": 0.5,
@@ -159,41 +256,58 @@
159
256
  "output": 8.0,
160
257
  "cache_read_input": 0.5,
161
258
  "batch_input": 1.0,
162
- "batch_output": 4.0
259
+ "batch_output": 4.0,
260
+ "priority_input": 3.5,
261
+ "priority_output": 14.0,
262
+ "priority_cache_read_input": 0.875
163
263
  },
164
264
  "openai/gpt-4.1-mini": {
165
265
  "input": 0.4,
166
266
  "output": 1.6,
167
267
  "cache_read_input": 0.1,
168
268
  "batch_input": 0.2,
169
- "batch_output": 0.8
269
+ "batch_output": 0.8,
270
+ "priority_input": 0.7,
271
+ "priority_output": 2.8,
272
+ "priority_cache_read_input": 0.175
170
273
  },
171
274
  "openai/gpt-4.1-nano": {
172
275
  "input": 0.1,
173
276
  "output": 0.4,
174
277
  "cache_read_input": 0.025,
175
278
  "batch_input": 0.05,
176
- "batch_output": 0.2
279
+ "batch_output": 0.2,
280
+ "priority_input": 0.2,
281
+ "priority_output": 0.8,
282
+ "priority_cache_read_input": 0.05
177
283
  },
178
284
  "openai/gpt-4o": {
179
285
  "input": 2.5,
180
286
  "output": 10.0,
181
287
  "cache_read_input": 1.25,
182
288
  "batch_input": 1.25,
183
- "batch_output": 5.0
289
+ "batch_output": 5.0,
290
+ "priority_input": 4.25,
291
+ "priority_output": 17.0,
292
+ "priority_cache_read_input": 2.125
184
293
  },
185
294
  "openai/gpt-4o-2024-05-13": {
186
295
  "input": 5.0,
187
296
  "output": 15.0,
188
297
  "batch_input": 2.5,
189
- "batch_output": 7.5
298
+ "batch_output": 7.5,
299
+ "priority_input": 8.75,
300
+ "priority_output": 26.25
190
301
  },
191
302
  "openai/gpt-4o-mini": {
192
303
  "input": 0.15,
193
304
  "output": 0.6,
194
305
  "cache_read_input": 0.075,
195
306
  "batch_input": 0.075,
196
- "batch_output": 0.3
307
+ "batch_output": 0.3,
308
+ "priority_input": 0.25,
309
+ "priority_output": 1.0,
310
+ "priority_cache_read_input": 0.125
197
311
  },
198
312
  "openai/gpt-5": {
199
313
  "input": 1.25,
@@ -201,7 +315,13 @@
201
315
  "cache_read_input": 0.125,
202
316
  "batch_input": 0.625,
203
317
  "batch_output": 5.0,
204
- "batch_cache_read_input": 0.0625
318
+ "batch_cache_read_input": 0.0625,
319
+ "flex_input": 0.625,
320
+ "flex_output": 5.0,
321
+ "flex_cache_read_input": 0.0625,
322
+ "priority_input": 2.5,
323
+ "priority_output": 20.0,
324
+ "priority_cache_read_input": 0.25
205
325
  },
206
326
  "openai/gpt-5-chat-latest": {
207
327
  "input": 1.25,
@@ -211,7 +331,10 @@
211
331
  "openai/gpt-5-codex": {
212
332
  "input": 1.25,
213
333
  "output": 10.0,
214
- "cache_read_input": 0.125
334
+ "cache_read_input": 0.125,
335
+ "priority_input": 2.5,
336
+ "priority_output": 20.0,
337
+ "priority_cache_read_input": 0.25
215
338
  },
216
339
  "openai/gpt-5-mini": {
217
340
  "input": 0.25,
@@ -219,7 +342,13 @@
219
342
  "cache_read_input": 0.025,
220
343
  "batch_input": 0.125,
221
344
  "batch_output": 1.0,
222
- "batch_cache_read_input": 0.0125
345
+ "batch_cache_read_input": 0.0125,
346
+ "flex_input": 0.125,
347
+ "flex_output": 1.0,
348
+ "flex_cache_read_input": 0.0125,
349
+ "priority_input": 0.45,
350
+ "priority_output": 3.6,
351
+ "priority_cache_read_input": 0.045
223
352
  },
224
353
  "openai/gpt-5-nano": {
225
354
  "input": 0.05,
@@ -227,7 +356,10 @@
227
356
  "cache_read_input": 0.005,
228
357
  "batch_input": 0.025,
229
358
  "batch_output": 0.2,
230
- "batch_cache_read_input": 0.0025
359
+ "batch_cache_read_input": 0.0025,
360
+ "flex_input": 0.025,
361
+ "flex_output": 0.2,
362
+ "flex_cache_read_input": 0.0025
231
363
  },
232
364
  "openai/gpt-5-pro": {
233
365
  "input": 15.0,
@@ -241,7 +373,13 @@
241
373
  "cache_read_input": 0.125,
242
374
  "batch_input": 0.625,
243
375
  "batch_output": 5.0,
244
- "batch_cache_read_input": 0.0625
376
+ "batch_cache_read_input": 0.0625,
377
+ "flex_input": 0.625,
378
+ "flex_output": 5.0,
379
+ "flex_cache_read_input": 0.0625,
380
+ "priority_input": 2.5,
381
+ "priority_output": 20.0,
382
+ "priority_cache_read_input": 0.25
245
383
  },
246
384
  "openai/gpt-5.1-chat-latest": {
247
385
  "input": 1.25,
@@ -251,12 +389,18 @@
251
389
  "openai/gpt-5.1-codex": {
252
390
  "input": 1.25,
253
391
  "output": 10.0,
254
- "cache_read_input": 0.125
392
+ "cache_read_input": 0.125,
393
+ "priority_input": 2.5,
394
+ "priority_output": 20.0,
395
+ "priority_cache_read_input": 0.25
255
396
  },
256
397
  "openai/gpt-5.1-codex-max": {
257
398
  "input": 1.25,
258
399
  "output": 10.0,
259
- "cache_read_input": 0.125
400
+ "cache_read_input": 0.125,
401
+ "priority_input": 2.5,
402
+ "priority_output": 20.0,
403
+ "priority_cache_read_input": 0.25
260
404
  },
261
405
  "openai/gpt-5.1-codex-mini": {
262
406
  "input": 0.25,
@@ -269,7 +413,13 @@
269
413
  "cache_read_input": 0.175,
270
414
  "batch_input": 0.875,
271
415
  "batch_output": 7.0,
272
- "batch_cache_read_input": 0.0875
416
+ "batch_cache_read_input": 0.0875,
417
+ "flex_input": 0.875,
418
+ "flex_output": 7.0,
419
+ "flex_cache_read_input": 0.0875,
420
+ "priority_input": 3.5,
421
+ "priority_output": 28.0,
422
+ "priority_cache_read_input": 0.35
273
423
  },
274
424
  "openai/gpt-5.2-chat-latest": {
275
425
  "input": 1.75,
@@ -279,7 +429,10 @@
279
429
  "openai/gpt-5.2-codex": {
280
430
  "input": 1.75,
281
431
  "output": 14.0,
282
- "cache_read_input": 0.175
432
+ "cache_read_input": 0.175,
433
+ "priority_input": 3.5,
434
+ "priority_output": 28.0,
435
+ "priority_cache_read_input": 0.35
283
436
  },
284
437
  "openai/gpt-5.2-pro": {
285
438
  "input": 21.0,
@@ -300,7 +453,37 @@
300
453
  "above_context_cache_read_input": 0.5,
301
454
  "above_context_batch_input": 2.5,
302
455
  "above_context_batch_output": 11.25,
303
- "above_context_batch_cache_read_input": 0.25
456
+ "above_context_batch_cache_read_input": 0.25,
457
+ "flex_input": 1.25,
458
+ "flex_output": 7.5,
459
+ "flex_cache_read_input": 0.13,
460
+ "above_context_flex_input": 2.5,
461
+ "above_context_flex_output": 11.25,
462
+ "above_context_flex_cache_read_input": 0.25,
463
+ "priority_input": 5.0,
464
+ "priority_output": 30.0,
465
+ "priority_cache_read_input": 0.5,
466
+ "data_residency_input": 2.75,
467
+ "data_residency_output": 16.5,
468
+ "data_residency_cache_read_input": 0.275,
469
+ "above_context_data_residency_input": 5.5,
470
+ "above_context_data_residency_output": 24.75,
471
+ "above_context_data_residency_cache_read_input": 0.55,
472
+ "batch_data_residency_input": 1.375,
473
+ "batch_data_residency_output": 8.25,
474
+ "batch_data_residency_cache_read_input": 0.143,
475
+ "above_context_batch_data_residency_input": 2.75,
476
+ "above_context_batch_data_residency_output": 12.375,
477
+ "above_context_batch_data_residency_cache_read_input": 0.275,
478
+ "flex_data_residency_input": 1.375,
479
+ "flex_data_residency_output": 8.25,
480
+ "flex_data_residency_cache_read_input": 0.143,
481
+ "above_context_flex_data_residency_input": 2.75,
482
+ "above_context_flex_data_residency_output": 12.375,
483
+ "above_context_flex_data_residency_cache_read_input": 0.275,
484
+ "priority_data_residency_input": 5.5,
485
+ "priority_data_residency_output": 33.0,
486
+ "priority_data_residency_cache_read_input": 0.55
304
487
  },
305
488
  "openai/gpt-5.4-mini": {
306
489
  "input": 0.75,
@@ -308,7 +491,25 @@
308
491
  "cache_read_input": 0.075,
309
492
  "batch_input": 0.375,
310
493
  "batch_output": 2.25,
311
- "batch_cache_read_input": 0.0375
494
+ "batch_cache_read_input": 0.0375,
495
+ "flex_input": 0.375,
496
+ "flex_output": 2.25,
497
+ "flex_cache_read_input": 0.0375,
498
+ "priority_input": 1.5,
499
+ "priority_output": 9.0,
500
+ "priority_cache_read_input": 0.15,
501
+ "data_residency_input": 0.825,
502
+ "data_residency_output": 4.95,
503
+ "data_residency_cache_read_input": 0.0825,
504
+ "batch_data_residency_input": 0.4125,
505
+ "batch_data_residency_output": 2.475,
506
+ "batch_data_residency_cache_read_input": 0.04125,
507
+ "flex_data_residency_input": 0.4125,
508
+ "flex_data_residency_output": 2.475,
509
+ "flex_data_residency_cache_read_input": 0.04125,
510
+ "priority_data_residency_input": 1.65,
511
+ "priority_data_residency_output": 9.9,
512
+ "priority_data_residency_cache_read_input": 0.165
312
513
  },
313
514
  "openai/gpt-5.4-nano": {
314
515
  "input": 0.2,
@@ -316,7 +517,19 @@
316
517
  "cache_read_input": 0.02,
317
518
  "batch_input": 0.1,
318
519
  "batch_output": 0.625,
319
- "batch_cache_read_input": 0.01
520
+ "batch_cache_read_input": 0.01,
521
+ "flex_input": 0.1,
522
+ "flex_output": 0.625,
523
+ "flex_cache_read_input": 0.01,
524
+ "data_residency_input": 0.22,
525
+ "data_residency_output": 1.375,
526
+ "data_residency_cache_read_input": 0.022,
527
+ "batch_data_residency_input": 0.11,
528
+ "batch_data_residency_output": 0.6875,
529
+ "batch_data_residency_cache_read_input": 0.011,
530
+ "flex_data_residency_input": 0.11,
531
+ "flex_data_residency_output": 0.6875,
532
+ "flex_data_residency_cache_read_input": 0.011
320
533
  },
321
534
  "openai/gpt-5.4-pro": {
322
535
  "input": 30.0,
@@ -327,7 +540,23 @@
327
540
  "above_context_input": 60.0,
328
541
  "above_context_output": 270.0,
329
542
  "above_context_batch_input": 30.0,
330
- "above_context_batch_output": 135.0
543
+ "above_context_batch_output": 135.0,
544
+ "flex_input": 15.0,
545
+ "flex_output": 90.0,
546
+ "above_context_flex_input": 30.0,
547
+ "above_context_flex_output": 135.0,
548
+ "data_residency_input": 33.0,
549
+ "data_residency_output": 198.0,
550
+ "above_context_data_residency_input": 66.0,
551
+ "above_context_data_residency_output": 297.0,
552
+ "batch_data_residency_input": 16.5,
553
+ "batch_data_residency_output": 99.0,
554
+ "above_context_batch_data_residency_input": 33.0,
555
+ "above_context_batch_data_residency_output": 148.5,
556
+ "flex_data_residency_input": 16.5,
557
+ "flex_data_residency_output": 99.0,
558
+ "above_context_flex_data_residency_input": 33.0,
559
+ "above_context_flex_data_residency_output": 148.5
331
560
  },
332
561
  "openai/gpt-5.5": {
333
562
  "input": 5.0,
@@ -342,7 +571,37 @@
342
571
  "above_context_cache_read_input": 1.0,
343
572
  "above_context_batch_input": 5.0,
344
573
  "above_context_batch_output": 22.5,
345
- "above_context_batch_cache_read_input": 0.5
574
+ "above_context_batch_cache_read_input": 0.5,
575
+ "flex_input": 2.5,
576
+ "flex_output": 15.0,
577
+ "flex_cache_read_input": 0.25,
578
+ "above_context_flex_input": 5.0,
579
+ "above_context_flex_output": 22.5,
580
+ "above_context_flex_cache_read_input": 0.5,
581
+ "priority_input": 12.5,
582
+ "priority_output": 75.0,
583
+ "priority_cache_read_input": 1.25,
584
+ "data_residency_input": 5.5,
585
+ "data_residency_output": 33.0,
586
+ "data_residency_cache_read_input": 0.55,
587
+ "above_context_data_residency_input": 11.0,
588
+ "above_context_data_residency_output": 49.5,
589
+ "above_context_data_residency_cache_read_input": 1.1,
590
+ "batch_data_residency_input": 2.75,
591
+ "batch_data_residency_output": 16.5,
592
+ "batch_data_residency_cache_read_input": 0.275,
593
+ "above_context_batch_data_residency_input": 5.5,
594
+ "above_context_batch_data_residency_output": 24.75,
595
+ "above_context_batch_data_residency_cache_read_input": 0.55,
596
+ "flex_data_residency_input": 2.75,
597
+ "flex_data_residency_output": 16.5,
598
+ "flex_data_residency_cache_read_input": 0.275,
599
+ "above_context_flex_data_residency_input": 5.5,
600
+ "above_context_flex_data_residency_output": 24.75,
601
+ "above_context_flex_data_residency_cache_read_input": 0.55,
602
+ "priority_data_residency_input": 13.75,
603
+ "priority_data_residency_output": 82.5,
604
+ "priority_data_residency_cache_read_input": 1.375
346
605
  },
347
606
  "openai/gpt-5.5-pro": {
348
607
  "input": 30.0,
@@ -351,7 +610,17 @@
351
610
  "batch_output": 90.0,
352
611
  "_context_price_threshold_tokens": 272000,
353
612
  "above_context_input": 60.0,
354
- "above_context_output": 270.0
613
+ "above_context_output": 270.0,
614
+ "flex_input": 15.0,
615
+ "flex_output": 90.0,
616
+ "data_residency_input": 33.0,
617
+ "data_residency_output": 198.0,
618
+ "above_context_data_residency_input": 66.0,
619
+ "above_context_data_residency_output": 297.0,
620
+ "batch_data_residency_input": 16.5,
621
+ "batch_data_residency_output": 99.0,
622
+ "flex_data_residency_input": 16.5,
623
+ "flex_data_residency_output": 99.0
355
624
  },
356
625
  "openai/o1": {
357
626
  "input": 15.0,
@@ -372,7 +641,13 @@
372
641
  "output": 8.0,
373
642
  "cache_read_input": 0.5,
374
643
  "batch_input": 1.0,
375
- "batch_output": 4.0
644
+ "batch_output": 4.0,
645
+ "flex_input": 1.0,
646
+ "flex_output": 4.0,
647
+ "flex_cache_read_input": 0.25,
648
+ "priority_input": 3.5,
649
+ "priority_output": 14.0,
650
+ "priority_cache_read_input": 0.875
376
651
  },
377
652
  "openai/o3-mini": {
378
653
  "input": 1.1,
@@ -386,7 +661,13 @@
386
661
  "output": 4.4,
387
662
  "cache_read_input": 0.275,
388
663
  "batch_input": 0.55,
389
- "batch_output": 2.2
664
+ "batch_output": 2.2,
665
+ "flex_input": 0.55,
666
+ "flex_output": 2.2,
667
+ "flex_cache_read_input": 0.138,
668
+ "priority_input": 2.0,
669
+ "priority_output": 8.0,
670
+ "priority_cache_read_input": 0.5
390
671
  },
391
672
  "anthropic/claude-haiku-3-5": {
392
673
  "input": 0.8,
@@ -430,7 +711,10 @@
430
711
  "openai/gpt-5.3-codex": {
431
712
  "input": 1.75,
432
713
  "output": 14.0,
433
- "cache_read_input": 0.175
714
+ "cache_read_input": 0.175,
715
+ "priority_input": 3.5,
716
+ "priority_output": 28.0,
717
+ "priority_cache_read_input": 0.35
434
718
  },
435
719
  "openai/codex-mini-latest": {
436
720
  "input": 1.5,
@@ -8,48 +8,54 @@ module LlmCostTracker
8
8
  class << self
9
9
  def call(usage:, prices:, pricing_mode:)
10
10
  quantities = usage.price_quantities
11
- context_tier = context_tier?(usage, prices)
11
+ context_tier = context_tier?(usage: usage, prices: prices)
12
12
 
13
13
  Pricing::COMPONENTS.to_h do |component|
14
14
  price_key = component.price_key
15
15
  tokens = quantities.fetch(price_key)
16
- price = tokens.positive? ? price_for(prices, price_key, pricing_mode, context_tier) : 0.0
16
+ price = if tokens.positive?
17
+ price_for(
18
+ prices: prices,
19
+ key: price_key,
20
+ pricing_mode: pricing_mode,
21
+ context_tier: context_tier
22
+ )
23
+ else
24
+ 0.0
25
+ end
17
26
  [price_key, price]
18
27
  end
19
28
  end
20
29
 
21
30
  private
22
31
 
23
- def price_for(prices, key, pricing_mode, context_tier)
32
+ def price_for(prices:, key:, pricing_mode:, context_tier:)
24
33
  mode = Pricing.normalize_mode(pricing_mode)
25
- return contextual_price(prices, key, context_tier) unless mode
34
+ return contextual_price(prices: prices, key: key, context_tier: context_tier) unless mode
26
35
 
27
- contextual_price(prices, :"#{mode}_#{key}", context_tier) ||
28
- derived_batch_price(prices, key, mode, context_tier)
36
+ contextual_price(prices: prices, key: :"#{mode}_#{key}", context_tier: context_tier) ||
37
+ derived_mode_price(prices: prices, key: key, mode: mode, context_tier: context_tier)
29
38
  end
30
39
 
31
- def contextual_price(prices, key, context_tier)
40
+ def contextual_price(prices:, key:, context_tier:)
32
41
  return prices[key] unless context_tier
33
42
 
34
43
  prices[:"above_context_#{key}"]
35
44
  end
36
45
 
37
- def derived_batch_price(prices, key, mode, context_tier)
38
- return nil unless mode == "batch"
39
-
40
- standard_price = contextual_price(prices, key, context_tier)
46
+ def derived_mode_price(prices:, key:, mode:, context_tier:)
47
+ standard_price = contextual_price(prices: prices, key: key, context_tier: context_tier)
41
48
  return nil unless standard_price
42
49
 
43
50
  base_key = key == :output ? :output : :input
44
- batch_key = key == :output ? :batch_output : :batch_input
45
- base_price = contextual_price(prices, base_key, context_tier)
46
- batch_price = contextual_price(prices, batch_key, context_tier)
47
- return nil unless base_price && batch_price
51
+ base_price = contextual_price(prices: prices, key: base_key, context_tier: context_tier)
52
+ mode_base_price = contextual_price(prices: prices, key: :"#{mode}_#{base_key}", context_tier: context_tier)
53
+ return nil unless base_price && mode_base_price
48
54
 
49
- standard_price * (batch_price.to_f / base_price)
55
+ standard_price * (mode_base_price.to_f / base_price)
50
56
  end
51
57
 
52
- def context_tier?(usage, prices)
58
+ def context_tier?(usage:, prices:)
53
59
  threshold = prices[:_context_price_threshold_tokens]
54
60
  return false unless threshold
55
61
 
@@ -36,12 +36,18 @@ module LlmCostTracker
36
36
  def call(provider:, model:, token_usage:, pricing_mode: nil)
37
37
  match = Lookup.call(provider: provider, model: model)
38
38
 
39
- explanation(provider, model, pricing_mode, match, token_usage)
39
+ explanation(
40
+ provider: provider,
41
+ model: model,
42
+ pricing_mode: pricing_mode,
43
+ match: match,
44
+ usage: token_usage
45
+ )
40
46
  end
41
47
 
42
48
  private
43
49
 
44
- def explanation(provider, model, pricing_mode, match, usage)
50
+ def explanation(provider:, model:, pricing_mode:, match:, usage:)
45
51
  prices = match&.prices
46
52
  pricing_mode = Pricing.normalize_mode(pricing_mode)
47
53
  effective = if prices && usage
@@ -49,15 +55,15 @@ module LlmCostTracker
49
55
  end
50
56
 
51
57
  Explanation.new(
52
- provider.to_s,
53
- model.to_s,
54
- pricing_mode,
55
- match&.source,
56
- match&.key,
57
- match&.matched_by,
58
- prices,
59
- effective || {},
60
- effective ? effective.filter_map { |key, value| key if value.nil? } : []
58
+ provider: provider.to_s,
59
+ model: model.to_s,
60
+ pricing_mode: pricing_mode,
61
+ source: match&.source,
62
+ matched_key: match&.key,
63
+ matched_by: match&.matched_by,
64
+ prices: prices,
65
+ effective_prices: effective || {},
66
+ missing_price_keys: effective ? effective.filter_map { |key, value| key if value.nil? } : []
61
67
  )
62
68
  end
63
69
  end