Skip to content

fix(parser): range-filter calls inside turns instead of dropping day-spanning turns - #853

Open
KENSHI601 wants to merge 2 commits into
getagentseal:mainfrom
KENSHI601:fix/turn-range-per-call-filter
Open

fix(parser): range-filter calls inside turns instead of dropping day-spanning turns#853
KENSHI601 wants to merge 2 commits into
getagentseal:mainfrom
KENSHI601:fix/turn-range-per-call-filter

Conversation

@KENSHI601

Copy link
Copy Markdown

Fixes #852.

Bug: parseProviderSources range-filtered turns by the turn's first call timestamp (turn.calls[0]?.timestamp). A long autonomous turn that starts before midnight (e.g. a 30h Codex run) was therefore dropped wholesale from the next day's view — today / single-day exports lost every post-midnight call in the turn.

Fix: filter calls inside the turn against the range instead. A turn is skipped only when none of its calls fall in range; otherwise it is kept with just the in-range calls, and downstream derivation (classified turn, project, sessionId, projectPath) uses the filtered turn. Applied to both identical filter blocks (served-sources loop and durable-orphans loop) in src/parser.ts.

Tests:

  • New regression test in tests/provider-turn-grouping.test.ts: a synthetic Codex rollout whose single turn has token_count events straddling midnight; querying the next day's range now keeps the turn with only the post-midnight call. Verified the test fails without the fix and passes with it.
  • tsc --noEmit and tsup build are green; parser-related suites (parser, session-cache, codex, date-range, export, durable-totals) pass. Full suite diffed against main: identical failure sets (pre-existing Windows-environment issues — missing jsdom, 5s-timeout flakes, os.homedir() fixtures), no new failures.

…spanning turns

parseProviderSources keyed the dateRange check on a turn's first call
timestamp, so a long autonomous turn starting before midnight was
excluded from the next day's view entirely and every post-midnight
call in it was lost. Filter calls inside the turn instead and keep
the turn when any call falls in range.

Fixes getagentseal#852
Extends the per-call range filter so multi-day periods stop losing
usage (review on getagentseal#857):

- Re-anchor a sliced turn's timestamp to its first surviving call in
  parseProviderSources, so every turn slicer shares one split rule.
- scanProjectDirs (Claude Code path): slice per call instead of
  dropping the whole turn on its first assistant timestamp; category/
  subCategory/retries/hasEdits stay classified from the full turn.
- aggregateProjectsIntoDays: bucket cost/calls/tokens (and the model,
  project, provider-slice rollups built from them) under each call's
  own day; turn-level stats (categories, editTurns, oneShotTurns) stay
  turn-anchored. This is the conservation fix: cache (<= yesterday) +
  live (today) unions now sum to the whole range for straddling turns.
- buildJsonReport's dailyMap fallback follows the same per-call rule
  so the no-durable path can't diverge from durable.days.
- filterProjectsByDateRange (dashboard) and filterProjectsByDays
  (menubar/history) slice per call instead of dropping whole turns.

Adds the straddling-turn buildDurablePeriod case to the durable-totals
parity suite (day-N + day-N+1 == whole-range calls/cost/tokens,
verified to fail without the fix), covers the today view and the
surface filters, and makes the suite hermetic on machines with real
provider data.
@KENSHI601

Copy link
Copy Markdown
Author

@iamtoruk Thanks for the thorough review on #857 — this update (7591e68) lands the conservation-correct version here, point by point:

  1. Conservation (blocker)aggregateProjectsIntoDays now buckets cost/savings/calls/tokens (and the model, project, and provider-slice rollups built from them) under each call's own day. Turn-level stats (categories, editTurns, oneShotTurns) stay turn-anchored, with the both-sides-of-a-split semantics documented in code. Your repro now reads today 1/$0.012, week/month 2/$0.018, with day-N + day-N+1 == whole range.
  2. Re-anchor — sliced turns get timestamp = first surviving call in both parseProviderSources loops, adopting parser: slice midnight-straddling turns instead of dropping them #857's split rule (callsInRange / turnSlicedToRange / classifiedTurnSlicedToRange, same null-on-empty contract).
  3. scanProjectDirs — slices per call instead of whole-turn drop; category/subCategory/retries/hasEdits stay classified from the full turn, per avs-io's approach.
  4. Regression test — added the straddling-turn buildDurablePeriod case to the durable-totals parity suite: day-N + day-N+1 == whole-range calls/cost/tokens, plus a today-only view case on the Claude path and surface-filter cases. Verified all three fail without the fix (including on this PR's previous revision) and pass with it.
  5. SurfacesfilterProjectsByDateRange (dashboard) and filterProjectsByDays (menubar/history) now slice per call too, so every surface reports the same Today number; buildJsonReport's dailyMap fallback follows the same per-call rule so it can't diverge from durable.days. turnIsInDateRange, orphaned by this change, is removed; parseSessionFile left untouched as agreed.

Verification: tsc --noEmit clean, tsup build ok, and the parser / day-aggregator / durable-totals / session-cache / codex / export suites are green — the durable-totals file is now also hermetic on machines with real provider data (USERPROFILE / KIMI_CODE_HOME / desktop-sessions redirects in beforeEach), which is what its 5s-timeout flakiness on this box actually was. Full-suite diff against the previous revision shows no new failures; remaining red is pre-existing environment noise (missing jsdom, homedir-based fixtures, load-sensitive timeouts).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Usage after local midnight vanishes when a turn spans days (turn-level range filter drops post-midnight calls)

1 participant