fix(models): keep merged baseline empty after a conflict

Extra High on #1053: three raw ids sharing a provider + display
name cleared then refilled savingsBaselineModel because empty
meant both "none seen" and "conflict". Track distinct baselines
in a Set and emit one only when a single non-empty value remains.
This commit is contained in:
Aditya Vikram Singh 2026-08-20 03:08:29 +05:30
parent 28be2472ce
commit 9e361175bb
2 changed files with 38 additions and 7 deletions

View file

@ -167,6 +167,10 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
const foldedCategoryCost = new Map<string, Map<CategoryKey, number>>()
const foldedTotalCost = new Map<string, number>()
const foldedRawSeen = new Set<string>()
// Empty string on the row means both "none seen" and "conflict". Track
// distinct non-empty baselines separately so a later bucket cannot
// repopulate a conflict that was already detected.
const baselinesByKey = new Map<string, Set<string>>()
for (const bucket of buckets.values()) {
const meta = await resolveProvider(bucket.provider)
@ -184,6 +188,11 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
})
: null
const baselines = baselinesByKey.get(resolvedKey) ?? new Set<string>()
if (bucket.savingsBaselineModel) baselines.add(bucket.savingsBaselineModel)
baselinesByKey.set(resolvedKey, baselines)
const resolvedBaseline = baselines.size === 1 ? [...baselines][0]! : ''
const existing = rowsByKey.get(resolvedKey)
if (existing) {
existing.inputTokens += bucket.inputTokens
@ -195,12 +204,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
existing.savingsUSD += bucket.savingsUSD
existing.calls += bucket.calls
if (bucket.model < existing.model) existing.model = bucket.model
if (existing.savingsBaselineModel && bucket.savingsBaselineModel
&& existing.savingsBaselineModel !== bucket.savingsBaselineModel) {
existing.savingsBaselineModel = ''
} else if (!existing.savingsBaselineModel && bucket.savingsBaselineModel) {
existing.savingsBaselineModel = bucket.savingsBaselineModel
}
existing.savingsBaselineModel = resolvedBaseline
if (existing.credits === null || bucketCredits === null) existing.credits = null
else existing.credits += bucketCredits
} else {
@ -218,7 +222,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
totalTokens: total,
costUSD: bucket.costUSD,
savingsUSD: bucket.savingsUSD,
savingsBaselineModel: bucket.savingsBaselineModel,
savingsBaselineModel: resolvedBaseline,
calls: bucket.calls,
credits: bucketCredits,
})

View file

@ -37,6 +37,8 @@ function makeCall(opts: {
output?: number
cacheWrite?: number
cacheRead?: number
savingsUSD?: number
savingsBaselineModel?: string
}): ParsedApiCall {
return {
provider: opts.provider,
@ -58,6 +60,8 @@ function makeCall(opts: {
timestamp: '2026-05-09T00:00:00.000Z',
bashCommands: [],
deduplicationKey: `${opts.provider}-${opts.model}-${opts.costUSD}`,
savingsUSD: opts.savingsUSD,
savingsBaselineModel: opts.savingsBaselineModel,
}
}
@ -228,6 +232,29 @@ describe('aggregateModels', () => {
expect(rows[0]!.topCategoryShare).toBeCloseTo(0.246 / 0.265, 3)
})
it('clears a merged savings baseline when three raw ids disagree', async () => {
const rows = await aggregateModels([makeProject([
makeTurn('feature', [makeCall({
provider: 'cline-cli', model: 'glm-5p2',
costUSD: 1, savingsUSD: 2, savingsBaselineModel: 'gpt-4o',
})]),
makeTurn('feature', [makeCall({
provider: 'cline-cli', model: 'GLM-5.2',
costUSD: 1, savingsUSD: 2, savingsBaselineModel: 'claude-sonnet-4-6',
})]),
makeTurn('feature', [makeCall({
provider: 'cline-cli', model: 'accounts/fireworks/models/glm-5p2',
costUSD: 1, savingsUSD: 2, savingsBaselineModel: 'gpt-5',
})]),
])])
expect(rows).toHaveLength(1)
expect(rows[0]!.provider).toBe('cline-cli')
expect(rows[0]!.modelDisplayName).toBe('GLM-5.2')
expect(rows[0]!.savingsUSD).toBe(6)
expect(rows[0]!.savingsBaselineModel).toBe('')
expect(rows[0]!.calls).toBe(3)
})
it('does not merge the same display name across providers', async () => {
const rows = await aggregateModels([makeProject([
makeTurn('feature', [makeCall({ provider: 'cline-cli', model: 'glm-5p2', costUSD: 1 })]),