Skip to content

Fix #348: bound API daily cost windows#403

Merged
anilmurty merged 1 commit into
Metabuilder-Labs:mainfrom
sanmaxdev:fix/348-api-daily-cost-window
Jul 4, 2026
Merged

Fix #348: bound API daily cost windows#403
anilmurty merged 1 commit into
Metabuilder-Labs:mainfrom
sanmaxdev:fix/348-api-daily-cost-window

Conversation

@sanmaxdev

Copy link
Copy Markdown
Contributor

ApiBackend.get_daily_cost was querying /api/v1/cost from the requested day's midnight through now, so historical dates could return cumulative spend instead of that single day's spend. This bounds the shim query to the requested UTC day and tightens the parity guard so the regression is caught.

Summary

  • Bound the API shim daily-cost request with both since=<day 00:00 UTC> and until=<next day 00:00 UTC>.
  • Updated the storage-backend parity spec to query the seeded historical day, not today, so a missing until bound turns red.

Related issue

Closes #348

Tests / Verification

  • PYTHONPATH=/tmp/tokenjam pytest tests/integration/test_storage_backend_parity.py::test_shim_matches_db tests/integration/test_storage_backend_parity.py::test_duckdb_and_in_memory_agree -q --tb=short
  • PYTHONPATH=/tmp/tokenjam pytest tests/integration/test_storage_backend_parity.py::test_every_protocol_method_is_classified tests/integration/test_storage_backend_parity.py::test_parity_methods_have_specs tests/integration/test_storage_backend_parity.py::test_parity_and_gap_methods_are_actually_implemented tests/integration/test_storage_backend_parity.py::test_unimplemented_methods_have_no_silent_shim -q --tb=short
  • PYTHONPATH=/tmp/tokenjam pytest tests/unit/ tests/synthetic/ tests/agents/ tests/integration/ -q --tb=short -p no:cacheprovider — 1916 passed
  • ruff check tokenjam/
  • mypy tokenjam/ --show-error-codes

Checklist

  • Tests pass (pytest tests/unit/ tests/synthetic/ tests/agents/ tests/integration/)
  • Lint clean (ruff check tokenjam/)
  • Type check clean (mypy tokenjam/)
  • CLAUDE.md updated (not needed, no architecture change)
  • Test spans use tests/factories.py (not raw NormalizedSpan)
  • Requested @anilmurty as reviewer (or @-mentioned him above)

@anilmurty ready for review.

Bound ApiBackend.get_daily_cost to the requested UTC calendar day instead of querying from day start through now. Update the storage backend parity guard to query the seeded historical day so the serve shim catches cumulative daily-cost regressions.
@sanmaxdev sanmaxdev requested a review from anilmurty as a code owner July 4, 2026 17:08
@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where ApiBackend.get_daily_cost issued an unbounded query to /api/v1/cost, causing historical dates to return cumulative spend from that day forward rather than the single day's spend. The parity test is also updated to query a seeded historical day so a missing until bound actually causes a test failure.

  • get_daily_cost now constructs a UTC-aware half-open interval [day 00:00 UTC, next day 00:00 UTC) and passes both since and until to the cost endpoint; the naive datetime (no tzinfo) is also replaced with an explicit timezone.utc datetime.
  • The parity spec is changed from querying now.date() (today) to a historical_day seeded 3 days prior, making the test load-bearing: without the until bound, the shim would aggregate the historical span and today's spans and diverge from the DB result.

Confidence Score: 5/5

Safe to merge — the change is a focused, two-line fix to a well-understood query bound bug, and the updated parity test now enforces the fix for any future regression.

Both the fix and the test update are correct. The UTC-aware half-open window [day 00:00, next day 00:00) is the right semantics. The parity test is now load-bearing: querying a historical day means a missing until causes the shim to return cumulative spend and diverges from the DB result. No unintended side effects were found in the surrounding code paths.

No files require special attention.

Important Files Changed

Filename Overview
tokenjam/core/api_backend.py get_daily_cost now builds a UTC-aware half-open [start, end) window and passes both since and until to /api/v1/cost, fixing the cumulative-spend regression
tests/integration/test_storage_backend_parity.py Parity spec now targets a seeded historical day (now-3d) instead of today; the dataset comment and spec comment are updated to match, and the test now actually catches a missing until bound

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI
    participant ApiBackend
    participant TjServe as tj serve /api/v1/cost

    Note over CLI,TjServe: Before fix (cumulative bug)
    CLI->>ApiBackend: "get_daily_cost(agent, day=2024-01-12)"
    ApiBackend->>TjServe: "GET /api/v1/cost?since=2024-01-12T00:00:00&group_by=day"
    TjServe-->>ApiBackend: "total_cost_usd = 99.0 + 6.0 (cumulative!)"
    ApiBackend-->>CLI: 105.0 ❌

    Note over CLI,TjServe: After fix (bounded window)
    CLI->>ApiBackend: "get_daily_cost(agent, day=2024-01-12)"
    ApiBackend->>ApiBackend: "start = 2024-01-12T00:00:00+00:00"
    ApiBackend->>ApiBackend: "end   = 2024-01-13T00:00:00+00:00"
    ApiBackend->>TjServe: "GET /api/v1/cost?since=2024-01-12T00:00:00+00:00&until=2024-01-13T00:00:00+00:00&group_by=day"
    TjServe-->>ApiBackend: "total_cost_usd = 99.0 (single day only)"
    ApiBackend-->>CLI: 99.0 ✅
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI
    participant ApiBackend
    participant TjServe as tj serve /api/v1/cost

    Note over CLI,TjServe: Before fix (cumulative bug)
    CLI->>ApiBackend: "get_daily_cost(agent, day=2024-01-12)"
    ApiBackend->>TjServe: "GET /api/v1/cost?since=2024-01-12T00:00:00&group_by=day"
    TjServe-->>ApiBackend: "total_cost_usd = 99.0 + 6.0 (cumulative!)"
    ApiBackend-->>CLI: 105.0 ❌

    Note over CLI,TjServe: After fix (bounded window)
    CLI->>ApiBackend: "get_daily_cost(agent, day=2024-01-12)"
    ApiBackend->>ApiBackend: "start = 2024-01-12T00:00:00+00:00"
    ApiBackend->>ApiBackend: "end   = 2024-01-13T00:00:00+00:00"
    ApiBackend->>TjServe: "GET /api/v1/cost?since=2024-01-12T00:00:00+00:00&until=2024-01-13T00:00:00+00:00&group_by=day"
    TjServe-->>ApiBackend: "total_cost_usd = 99.0 (single day only)"
    ApiBackend-->>CLI: 99.0 ✅
Loading

Reviews (1): Last reviewed commit: "Fix #348 daily cost API shim window" | Re-trigger Greptile

@anilmurty anilmurty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sanmaxdev — merging. The best part of this PR is the test retarget: moving the parity spec from now.date() to a seeded historical day is what makes the missing 'until' bound actually exercisable — the old test structurally couldn't catch this. Verified the assertion fails without the fix (105.0 vs 99.0 on the seeded data). Clean, exactly scoped to #348.

@anilmurty anilmurty merged commit df1b2ff into Metabuilder-Labs:main Jul 4, 2026
4 checks passed
@sanmaxdev sanmaxdev deleted the fix/348-api-daily-cost-window branch July 5, 2026 03:46
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.

ApiBackend.get_daily_cost sums from day-start to now instead of bounding to a single day

2 participants