Merge pull request #477 from STiFLeR7/fix/session-trends-success-delta-473

fix(metrics): claw back sessionsSucceeded when a resumed session later fails
This commit is contained in:
jeff
2026-09-10 13:24:26 +08:00
committed by GitHub
3 changed files with 32 additions and 2 deletions
+23
View File
@@ -61,6 +61,29 @@ describe('daily session trends', () => {
expect(second.delta['2026-09-03']).toMatchObject({ pricedRequests: 1, costMicros: 150, sessionsEnded: 0 });
});
it('claws back sessionsSucceeded when a resumed session later fails (#473)', () => {
const succeeded = new Map([
['s1', { date: '2026-09-02', prompts: 1, durationMs: 60_000, succeeded: 1 as const, corrected: 0 as const, requestDaily: {} }],
]);
const first = computeDailyStatsDelta(succeeded, {});
expect(first.delta['2026-09-02']).toMatchObject({ sessionsEnded: 1, sessionsSucceeded: 1 });
const merged = mergeDailyStats(undefined, first.delta);
expect(merged['2026-09-02']).toMatchObject({ sessionsEnded: 1, sessionsSucceeded: 1 });
const interrupted = new Map([
['s1', { ...succeeded.get('s1')!, succeeded: 0 as const, corrected: 1 as const }],
]);
const second = computeDailyStatsDelta(interrupted, first.nextReported);
expect(second.delta['2026-09-02']).toMatchObject({ sessionsEnded: 0, sessionsSucceeded: -1, sessionsCorrected: 1 });
const remerged = mergeDailyStats(merged, second.delta);
expect(remerged['2026-09-02']).toMatchObject({ sessionsEnded: 1, sessionsSucceeded: 0 });
// A later, unrelated re-report of the same now-failed state must not
// double-subtract: the delta settles back to 0 once the baseline catches up.
const third = computeDailyStatsDelta(interrupted, second.nextReported);
expect(third.delta['2026-09-02']).toMatchObject({ sessionsEnded: 0, sessionsSucceeded: 0 });
});
it('compares the latest seven UTC days with the prior seven days', () => {
const daily: Record<string, DailyUserStats> = {
'2026-08-27': { sessionsEnded: 10, sessionsSucceeded: 5, promptTurns: 80, durationMs: 600_000, sessionsCorrected: 4, pricedRequests: 10, costMicros: 1_000_000, cacheReadTokens: 20, cacheEligibleInputTokens: 100 },
+5 -1
View File
@@ -104,8 +104,12 @@ export function computeDailyStatsDelta(
const bucket = delta[date] ?? emptyDaily();
if (!previous) {
bucket.sessionsEnded += 1;
bucket.sessionsSucceeded += snapshot.succeeded;
}
// Signed, not positiveDelta: unlike the monotonic counters below, a
// session can flip from succeeded to failed on a later report (resumed
// after an interruption/correction), and that must claw back the earlier
// sessionsSucceeded increment, not just skip adding a new one (#473).
bucket.sessionsSucceeded += snapshot.succeeded - (previous?.succeeded ?? 0);
bucket.promptTurns += positiveDelta(snapshot.prompts, previous?.prompts);
bucket.durationMs += positiveDelta(snapshot.durationMs, previous?.durationMs);
bucket.sessionsCorrected += positiveDelta(snapshot.corrected, previous?.corrected);
+4 -1
View File
@@ -298,7 +298,10 @@ async function writeReportedDailySessions(data: ReportedDailySessions): Promise<
function hasDailyDelta(delta: ReturnType<typeof computeDailyStatsDelta>['delta']): boolean {
return Object.values(delta).some((bucket) =>
bucket.sessionsEnded > 0 || bucket.sessionsSucceeded > 0 || bucket.promptTurns > 0
// sessionsSucceeded can be negative (a resumed session that later failed
// claws back an earlier increment), so it must not be checked with the
// same "> 0" as the other, purely monotonic counters (#473).
bucket.sessionsEnded > 0 || bucket.sessionsSucceeded !== 0 || bucket.promptTurns > 0
|| bucket.durationMs > 0 || bucket.sessionsCorrected > 0 || bucket.pricedRequests > 0
|| bucket.costMicros > 0 || bucket.cacheReadTokens > 0 || bucket.cacheEligibleInputTokens > 0,
);