mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-30 02:43:34 +00:00
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:
parent
28be2472ce
commit
9e361175bb
2 changed files with 38 additions and 7 deletions
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 })]),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue