mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-14 16:12:13 +00:00
fix: bucket turns by assistant timestamp, filter at turn level
A turn that straddles midnight (user typed at 23:58, assistant responded at 00:30) was bucketed and filtered inconsistently across call sites. parseSessionFile filtered entries by timestamp, producing orphan assistant calls that groupIntoTurns pushed as turns with empty timestamp. Some downstream code counted those (buildPeriodData summing project totals) and other code dropped them (renderStatusBar's empty-timestamp skip). The menubar showed today = $32 while the terminal status showed today = $27 for the same dataset; each was internally consistent but used a different turn-bucket rule. Fix both: parseSessionFile now builds all turns first, then filters each turn by its first assistant call timestamp (the moment cost was incurred). renderStatusBar buckets the same way. day-aggregator.ts already bucketed on assistant time, so it is now consistent too. Net effect: a turn is counted in the day the API call actually ran in.
This commit is contained in:
parent
68e9c63088
commit
b491a1f590
2 changed files with 22 additions and 14 deletions
|
|
@ -34,12 +34,15 @@ export function renderStatusBar(projects: ProjectSummary[]): string {
|
|||
for (const project of projects) {
|
||||
for (const session of project.sessions) {
|
||||
for (const turn of session.turns) {
|
||||
if (!turn.timestamp) continue
|
||||
// Bucket by the session timestamp's local date so the user's "today" and "this month"
|
||||
// match the wall clock on their machine. Session timestamps are stored as UTC ISO
|
||||
// strings; naively slicing `timestamp.slice(0,10)` bucketed them by UTC date, which
|
||||
// showed `Today $0` during the UTC-midnight-to-local-midnight window.
|
||||
const day = localDateString(new Date(turn.timestamp))
|
||||
if (turn.assistantCalls.length === 0) continue
|
||||
// Bucket by the first assistant call's local date -- the moment the cost was
|
||||
// incurred. Bucketing by `turn.timestamp` (the user message time) drops turns
|
||||
// that straddle midnight (user asked at 23:58, response arrived at 00:30) and
|
||||
// disagrees with parseAllSessions' dateRange filter which is also on assistant
|
||||
// time.
|
||||
const bucketTs = turn.assistantCalls[0]!.timestamp
|
||||
if (!bucketTs) continue
|
||||
const day = localDateString(new Date(bucketTs))
|
||||
const turnCost = turn.assistantCalls.reduce((s, c) => s + c.costUSD, 0)
|
||||
const turnCalls = turn.assistantCalls.length
|
||||
if (day === today) { todayCost += turnCost; todayCalls += turnCalls }
|
||||
|
|
|
|||
|
|
@ -287,18 +287,23 @@ async function parseSessionFile(
|
|||
|
||||
if (entries.length === 0) return null
|
||||
|
||||
let filteredEntries = entries
|
||||
const sessionId = basename(filePath, '.jsonl')
|
||||
let turns = groupIntoTurns(entries, seenMsgIds)
|
||||
if (dateRange) {
|
||||
filteredEntries = entries.filter(e => {
|
||||
if (!e.timestamp) return e.type === 'user'
|
||||
const ts = new Date(e.timestamp)
|
||||
// Bucket a turn by the timestamp of its first assistant call (when the cost was
|
||||
// actually incurred). Filtering entries directly produced orphan assistant calls
|
||||
// when a user message sat in one day and the response landed in another -- those
|
||||
// got pushed as turns with empty timestamps, which some code paths counted and
|
||||
// others dropped, producing inconsistent Today totals.
|
||||
turns = turns.filter(turn => {
|
||||
if (turn.assistantCalls.length === 0) return false
|
||||
const firstCallTs = turn.assistantCalls[0]!.timestamp
|
||||
if (!firstCallTs) return false
|
||||
const ts = new Date(firstCallTs)
|
||||
return ts >= dateRange.start && ts <= dateRange.end
|
||||
})
|
||||
if (filteredEntries.length === 0) return null
|
||||
if (turns.length === 0) return null
|
||||
}
|
||||
|
||||
const sessionId = basename(filePath, '.jsonl')
|
||||
const turns = groupIntoTurns(filteredEntries, seenMsgIds)
|
||||
const classified = turns.map(classifyTurn)
|
||||
|
||||
return buildSessionSummary(sessionId, project, classified)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue