(usagePaths = [], rawBudget, target)
| 238 | } |
| 239 | |
| 240 | export async function analyzeObservedUsage(usagePaths = [], rawBudget, target) { |
| 241 | if (!Array.isArray(usagePaths) || usagePaths.length === 0) { |
| 242 | return null; |
| 243 | } |
| 244 | |
| 245 | const snapshots = []; |
| 246 | for (const usagePath of usagePaths) { |
| 247 | const loaded = await loadSnapshotsFromFile(usagePath); |
| 248 | snapshots.push(...loaded); |
| 249 | } |
| 250 | |
| 251 | if (snapshots.length === 0) { |
| 252 | throw new Error("Observed usage files were provided, but no usage payloads could be parsed."); |
| 253 | } |
| 254 | |
| 255 | const inputTokens = buildStat(snapshots.map((snapshot) => snapshot.inputTokens)); |
| 256 | const outputTokens = buildStat(snapshots.map((snapshot) => snapshot.outputTokens)); |
| 257 | const totalTokens = buildStat(snapshots.map((snapshot) => snapshot.totalTokens)); |
| 258 | const cachedTokens = buildStat(snapshots.map((snapshot) => snapshot.cachedTokens)); |
| 259 | const reasoningTokens = buildStat(snapshots.map((snapshot) => snapshot.reasoningTokens)); |
| 260 | const estimatedActiveTokens = rawBudget.trigger_cost_tokens.value + rawBudget.invoke_cost_tokens.value; |
| 261 | const observedAverageInputTokens = inputTokens.average; |
| 262 | const deltaTokens = round(observedAverageInputTokens - estimatedActiveTokens); |
| 263 | const deltaRatio = |
| 264 | estimatedActiveTokens > 0 ? round(Math.abs(deltaTokens) / estimatedActiveTokens) : 0; |
| 265 | |
| 266 | const observedUsage = { |
| 267 | method: "observed-usage-files", |
| 268 | target: { |
| 269 | name: target.name, |
| 270 | kind: target.kind, |
| 271 | path: target.path, |
| 272 | relativePath: relativePath(process.cwd(), target.path), |
| 273 | }, |
| 274 | sampleCount: snapshots.length, |
| 275 | files: [...new Set(snapshots.map((snapshot) => relativePath(process.cwd(), snapshot.sourcePath)))], |
| 276 | inputTokens, |
| 277 | outputTokens, |
| 278 | totalTokens, |
| 279 | cachedTokens, |
| 280 | reasoningTokens, |
| 281 | estimateComparison: estimatedActiveTokens |
| 282 | ? { |
| 283 | estimatedActiveTokens, |
| 284 | observedAverageInputTokens, |
| 285 | deltaTokens, |
| 286 | deltaRatio, |
| 287 | band: classifyEstimateAlignment(deltaRatio), |
| 288 | } |
| 289 | : null, |
| 290 | samples: snapshots.map((snapshot) => ({ |
| 291 | ...snapshot, |
| 292 | sourcePath: relativePath(process.cwd(), snapshot.sourcePath), |
| 293 | })), |
| 294 | }; |
| 295 | |
| 296 | const metrics = [ |
| 297 | createMetric({ |
no test coverage detected