Skip to content

Account Token Activity: add a non-mixing UTC daily-bucket fallback #53

Description

@thrr87

Parent: #46

Blocked by: #50
Parent user stories covered: 30–34

Outcome

When no valid lifetime-token interval can be formed for the selected range, Account Token Activity may fall back to complete UTC daily buckets. The fallback never mixes with lifetime-derived intervals, never counts a partial bucket, and presents the UTC interval boundaries in the Mac's current timezone without calling them local calendar days.

This issue retains the existing API-provided daily data as a weaker factual source. It does not change local Token Activity or add calendar-day presets.

Existing code to start from

  • AccountTokenActivityMethod.dailyBuckets and dailyAccountTokenActivity(...) in Sources/CodexLimits/UsageIntelligenceEngine.swift.
  • UsageSnapshot.tokenHistory and the existing daily fallback tests in UsageIntelligenceEngineTests.swift.
  • Account interval selection from Account Token Activity: render truthful rolling 24-hour intervals #50.
  • Account card/source text in TokenActivityWorkspace in MenuContentView.swift.
  • UTC helpers already present in UsageHistory.swift; reuse Foundation Calendar/fixed UTC only to model source intervals, not reader-facing labels.

Source precedence

For one selected range, choose exactly one method:

  1. If at least one valid lifetime Token Activity Interval is fully contained, use all contained lifetime intervals and ignore daily buckets.
  2. Otherwise, consider daily buckets.
  3. Include a daily bucket only if its entire actual UTC interval [00:00Z, next 00:00Z) lies inside the selected range.
  4. Sum included daily buckets. Do not combine them with any lifetime delta.
  5. If neither method supplies a complete interval, return No account readings in this range.

A lifetime reading pair outside the selected range does not count as an available interval inside it. Precedence is evaluated for the selected range, not globally.

Presentation contract

  • Method/provenance says the value comes from Codex UTC daily token totals.
  • Display the bucket's actual start/end formatted using the Mac's current timezone.
  • Do not label a UTC bucket as “Aug 2 local day” when its local boundaries span parts of two dates.
  • The primary card stays simple; source mechanics belong in Evidence Details.
  • Missing partial bucket time remains empty. Never scale a daily total to fit a partial range.

Acceptance criteria

  • One contained lifetime interval wins even if daily buckets also exist.
  • Lifetime and daily values are never present in the same selected total.
  • With no lifetime interval, one or more fully contained UTC buckets are summed.
  • A bucket crossing the selected start or end is excluded in full.
  • A rolling 24-hour range with no full UTC bucket and no lifetime interval is unavailable with the specific reason.
  • UTC interval boundaries render correctly in UTC, CET, and CEST.
  • A timezone label change does not alter which absolute bucket interval is included.
  • No data is proportionally allocated.
  • Existing daily API parsing remains unchanged.

Required tests

At the reader snapshot seam add cases for lifetime precedence, daily-only fallback, overlapping sources, boundary-crossing buckets on both sides, no contained source, multiple buckets, and timezone/DST presentation. Assert method, total, interval, and reader-visible source wording.

Run:

swift test --filter UsageIntelligenceEngineTests

swift test --filter AnalyticsWorkspaceTests

Documentation

Update the daily-bucket fallback section in docs/MEASUREMENT-CONTRACT.md and the source wording in docs/PRODUCT-LANGUAGE.md.

Out of scope

  • Mixing sources to increase coverage.
  • Splitting or estimating partial buckets.
  • Changing Codex API parsing or storage.
  • Adding local-calendar daily buckets.
  • Token forecasts or limits.

Shared delivery contract

  • Implement only on the shared analytics-fixes branch. Reuse the branch and all earlier completed child-issue commits.
  • Keep this issue as one isolated commit whose message references the issue number.
  • Do not create another branch or pull request. Do not merge, tag, publish, or release.
  • Do not close the issue manually. The single final pull request will reference all child issues; they close only when that pull request is eventually merged.
  • Preserve unrelated working-tree changes. Do not reformat or refactor files outside this issue's scope.
  • Do not add a dependency, persistence format, feature flag, protocol, or abstraction unless the acceptance criteria cannot be met by the existing types and standard library.
  • Run the issue-specific checks below before committing. The final full-suite and integrated UI verification happen only after all child issues are implemented.
  • Before finishing, scan reader-visible strings touched by this issue and remove debug text, TODOs, implementation notes, and internal terminology not explicitly required by the product contract.

Handoff evidence

The implementing agent must report:

  1. the commit hash and exact files changed;
  2. the commands run and whether they passed;
  3. each acceptance criterion as pass/fail;
  4. any remaining risk or dependency, without expanding this issue's scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentReady for implementation by an agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions