Skip to content

Fix Estimated API value custom date range - #173

Merged
tsouth89 merged 2 commits into
mainfrom
fix/api-value-custom-range-data
Jul 28, 2026
Merged

Fix Estimated API value custom date range#173
tsouth89 merged 2 commits into
mainfrom
fix/api-value-custom-range-data

Conversation

@tsouth89

@tsouth89 tsouth89 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Custom date range on Estimated API value was showing No data even when local logs had spend for that window (reproduced on a machine where the scanner returned thousands of dollars for the same dates).

Fixes

  • Attach a scanned dailySeries and fall back to summing those days when the custom window is empty
  • Only pass since/until when both dates are set
  • Keep ring layout while loading; cleaner custom date bar with range pill
  • Codex: credit current windows before requiring a daily bucket key

Test plan

  • Frontend unit tests
  • Rust custom-range + cost_scanner tests
  • Manual: Charts → Custom → month range → ring and legend show dollars; trend follows selected range

Summary by CodeRabbit

  • New Features

    • Added custom date-range support for Estimated API Value totals and trends.
    • Custom ranges now include daily activity and per-provider breakdowns, even when aggregated data is unavailable.
    • Added clearer date-range labeling and selection controls.
  • Bug Fixes

    • Improved usage accounting when daily records are incomplete.
    • Fixed date-range handling and empty-result behavior.
  • Style

    • Updated the custom-range layout with clearer spacing, separators, and range styling.
    • Added loading and error states for API value cards.

Custom ranges now fall back to the scanned daily dollar series when the window key is empty, always send real since/until to the backend, keep the ring layout while loading, and show a cleaner custom date bar with a range pill.
Copilot AI review requested due to automatic review settings July 28, 2026 01:30
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds daily API-value series to local chart data, derives inclusive custom-range totals from those series, and updates the API value card to render custom-period totals, trends, loading states, errors, and range-specific UI.

Changes

Custom API value ranges

Layer / File(s) Summary
Backend daily-series aggregation
rust/src/cost_scanner.rs, apps/desktop-tauri/src-tauri/src/commands/chart.rs
Rollup accounting continues when daily buckets are missing; chart providers expose daily series and derive inclusive custom-range periods.
Frontend custom-period selection
apps/desktop-tauri/src/types/bridge.ts, apps/desktop-tauri/src/lib/apiValueCard.ts, apps/desktop-tauri/src/lib/tauri.ts
Bridge types and card helpers aggregate daily values for custom ranges and normalize optional date arguments.
Card loading, trends, and range states
apps/desktop-tauri/src/components/TotalApiValueCard.tsx
The card computes selected-period trends, handles loading and errors, synthesizes missing custom data, and renders range-specific totals and captions.
Custom-range UI validation and styling
apps/desktop-tauri/src/components/TotalApiValueCard.test.tsx, apps/desktop-tauri/src/lib/apiValueCard.test.ts, apps/desktop-tauri/src/styles.css
Tests cover daily-series fallback behavior, and styles add the custom-range separator, container, and range pill.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TotalApiValueCard
  participant getLocalApiValueTotals
  participant get_local_api_value_totals
  participant LocalApiValueProvider
  TotalApiValueCard->>getLocalApiValueTotals: request selected date range
  getLocalApiValueTotals->>get_local_api_value_totals: forward complete range
  get_local_api_value_totals->>LocalApiValueProvider: return daily_series and periods
  TotalApiValueCard->>TotalApiValueCard: aggregate custom period and build trends
Loading

Possibly related PRs

  • tsouth89/ceiling#57: Introduced the foundational local API-value command, DTOs, and bridge extended by this change.
  • tsouth89/ceiling#58: Added the related API-value card implementation that this change extends for custom ranges.

Suggested reviewers: finesssee

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing Estimated API value custom date range handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/api-value-custom-range-data

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ceiling 0c30551 Commit Preview URL

Branch Preview URL
Jul 28 2026, 01:54 AM

Copilot AI 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.

Pull request overview

Fixes “Estimated API value” custom date ranges showing No data by ensuring scanned daily series is available and by making custom-range selection more robust across the frontend ↔ Tauri command boundary.

Changes:

  • Backend: ensure custom-window totals aren’t dropped when a daily bucket is missing; expose a scanned daily_series and use it as a fallback for empty custom windows.
  • Frontend: only send since/until when both are present; synthesize custom totals from dailySeries when needed; update card UI for custom range controls and loading behavior.
  • Tests/CSS: add unit tests for the new fallback behavior and update styling for the custom date range bar.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
rust/src/cost_scanner.rs Fixes window aggregation so custom/reset windows are credited even if a daily bucket is absent.
apps/desktop-tauri/src-tauri/src/commands/chart.rs Adds daily_series, increases default scan horizon, and falls back to summing daily series when custom window is empty.
apps/desktop-tauri/src/types/bridge.ts Extends the bridge type with dailySeries for UI fallback/trend building.
apps/desktop-tauri/src/lib/tauri.ts Omits since/until args unless both are set (avoids null/empty range calls).
apps/desktop-tauri/src/lib/apiValueCard.ts Adds a dailySeries→period fallback for custom ranges in the model builder.
apps/desktop-tauri/src/lib/apiValueCard.test.ts Adds unit test coverage for dailySeries fallback when custom is empty.
apps/desktop-tauri/src/components/TotalApiValueCard.tsx Uses the new fallback/synth logic, refines custom range UI, and adjusts loading/error presentation.
apps/desktop-tauri/src/components/TotalApiValueCard.test.tsx Updates tests for custom-range loading behavior and dailySeries presence.
apps/desktop-tauri/src/styles.css Styles the custom range bar (pill, separator, background).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rust/src/cost_scanner.rs
Comment on lines +1461 to +1465
let Some(day_summary) = self.daily.get_mut(&record.day_key) else {
continue;
};
if let Some(cost) = add_codex_record_to_summary(day_summary, record) {
day_summary.total_cost_usd += cost;
Comment on lines 175 to +179
const rows = providers
.map((provider) => ({ provider, period: periodOf(provider, periodKey) }))
.map((provider) => ({
provider,
period: pickPeriod(provider, periodKey, customRange),
}))
)}

{safeModel.isEmpty || rangeError ? (
{loading ? (
Prefer daily-series fallback when the custom window is empty, add unit tests that sum inclusive day ranges, and satisfy clippy::redundant_closure.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/desktop-tauri/src/components/TotalApiValueCard.tsx (1)

176-198: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The "dailySeries else lastSevenDays" custom fallback is implemented three times. The same selection rule lives in the load effect, in periodOf, and in buildTrend; since buildApiValueCard is already called with customRange, the effect's copy is redundant and the three must now be kept in sync by hand.

  • apps/desktop-tauri/src/components/TotalApiValueCard.tsx#L176-L198: drop the synthesis block and just setProviders(rows)buildApiValueCard(providers, period, metric, customRange) already fills the custom period from dailySeries.
  • apps/desktop-tauri/src/lib/apiValueCard.ts#L106-L117: extract the series pick into an exported helper (e.g. customSeriesOf(provider)) and use it here.
  • apps/desktop-tauri/src/components/TotalApiValueCard.tsx#L113-L119: consume that same helper in buildTrend instead of repeating the ternary.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop-tauri/src/components/TotalApiValueCard.tsx` around lines 176 -
198, The custom-series fallback is duplicated across the load effect, periodOf,
and buildTrend; centralize it in an exported customSeriesOf(provider) helper. In
apps/desktop-tauri/src/components/TotalApiValueCard.tsx lines 176-198, remove
the synthesis block and always call setProviders(rows); in
apps/desktop-tauri/src/lib/apiValueCard.ts lines 106-117, extract and use the
helper; in apps/desktop-tauri/src/components/TotalApiValueCard.tsx lines
113-119, replace the local ternary with that helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop-tauri/src-tauri/src/commands/chart.rs`:
- Around line 948-993: Fix the daily-derived custom-period path so it does not
report fabricated zero token counts: update daily_series_from_report and its
callers to use per-day token data from the report, or preserve the window’s
token figures when unavailable and mark pricing coverage as unknown. Ensure
period_from_daily_series does not set tokens, priced_tokens, or total_tokens to
zero for dollar-only data.

In `@apps/desktop-tauri/src/components/TotalApiValueCard.tsx`:
- Around line 99-103: Update dayLabel to accept whether the range is custom and
return a month/day label for custom periods, while preserving weekday labels for
other periods and the existing invalid-date fallback. Pass period === "custom"
from buildTrend when constructing each TrendDay so custom-range axis labels
distinguish dates across multiple weeks.
- Around line 243-249: Update the loading branch in TotalApiValueCard so
subsequent loading states preserve the existing ring, legend, and trend layout
by rendering safeModel inside a dimmed or aria-busy wrapper instead of replacing
the body with a status paragraph. Remove the now-redundant initial loading guard
around the component’s early return, while retaining an appropriate first-load
empty state when no model exists.

---

Nitpick comments:
In `@apps/desktop-tauri/src/components/TotalApiValueCard.tsx`:
- Around line 176-198: The custom-series fallback is duplicated across the load
effect, periodOf, and buildTrend; centralize it in an exported
customSeriesOf(provider) helper. In
apps/desktop-tauri/src/components/TotalApiValueCard.tsx lines 176-198, remove
the synthesis block and always call setProviders(rows); in
apps/desktop-tauri/src/lib/apiValueCard.ts lines 106-117, extract and use the
helper; in apps/desktop-tauri/src/components/TotalApiValueCard.tsx lines
113-119, replace the local ternary with that helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eaf2b3fd-21a3-465d-8412-bd25526b2ed6

📥 Commits

Reviewing files that changed from the base of the PR and between bafaf42 and 0c30551.

📒 Files selected for processing (9)
  • apps/desktop-tauri/src-tauri/src/commands/chart.rs
  • apps/desktop-tauri/src/components/TotalApiValueCard.test.tsx
  • apps/desktop-tauri/src/components/TotalApiValueCard.tsx
  • apps/desktop-tauri/src/lib/apiValueCard.test.ts
  • apps/desktop-tauri/src/lib/apiValueCard.ts
  • apps/desktop-tauri/src/lib/tauri.ts
  • apps/desktop-tauri/src/styles.css
  • apps/desktop-tauri/src/types/bridge.ts
  • rust/src/cost_scanner.rs

Comment on lines +948 to +993
fn daily_series_from_report(daily_costs: &[(String, f64)]) -> Vec<LocalApiValueDay> {
let mut days: Vec<LocalApiValueDay> = daily_costs
.iter()
.map(|(date, api_value_usd)| LocalApiValueDay {
date: date.clone(),
api_value_usd: *api_value_usd,
tokens: 0,
})
.collect();
days.sort_by(|left, right| left.date.cmp(&right.date));
days
}

/// Inclusive local-calendar sum from the daily dollar series.
fn period_from_daily_series(
daily_series: &[LocalApiValueDay],
start: NaiveDate,
end_inclusive: NaiveDate,
) -> LocalApiValuePeriod {
if start > end_inclusive {
return empty_api_value_period();
}
let mut api_value_usd = 0.0;
let mut tokens: u64 = 0;
let mut has_data = false;
for day in daily_series {
let Ok(date) = NaiveDate::parse_from_str(&day.date, "%Y-%m-%d") else {
continue;
};
if date < start || date > end_inclusive {
continue;
}
if day.api_value_usd > 0.0 || day.tokens > 0 {
has_data = true;
}
api_value_usd += day.api_value_usd;
tokens = tokens.saturating_add(day.tokens);
}
LocalApiValuePeriod {
api_value_usd,
tokens,
priced_tokens: tokens,
total_tokens: tokens,
has_data: has_data || api_value_usd > 0.0,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Daily-derived custom periods report zero tokens.

daily_series_from_report hardcodes tokens: 0 because report.daily_costs only carries dollars, so any custom range that falls back to period_from_daily_series (line 886/892) yields tokens = priced_tokens = total_tokens = 0 while api_value_usd > 0. Switching the card's metric to Tokens then renders 0 for a range that visibly has dollars, and pricing coverage silently disappears. Either extend the report to expose per-day token counts, or make the fallback keep the window's token figures / mark coverage as unknown instead of 0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop-tauri/src-tauri/src/commands/chart.rs` around lines 948 - 993,
Fix the daily-derived custom-period path so it does not report fabricated zero
token counts: update daily_series_from_report and its callers to use per-day
token data from the report, or preserve the window’s token figures when
unavailable and mark pricing coverage as unknown. Ensure
period_from_daily_series does not set tokens, priced_tokens, or total_tokens to
zero for dollar-only data.

Comment on lines +99 to +103
function dayLabel(date: string): string {
const parsed = new Date(`${date}T12:00:00`);
if (Number.isNaN(parsed.getTime())) return date.slice(5);
return WEEKDAYS[parsed.getDay()];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Weekday labels repeat across multi-week custom ranges.

dayLabel always renders a weekday name, so a custom range spanning more than a week produces an axis of repeated "Mon/Tue/…" with no way to tell the weeks apart (only the bar title disambiguates). Consider a month/day label when period === "custom".

Proposed fix
-function dayLabel(date: string): string {
+function dayLabel(date: string, compact = false): string {
   const parsed = new Date(`${date}T12:00:00`);
   if (Number.isNaN(parsed.getTime())) return date.slice(5);
-  return WEEKDAYS[parsed.getDay()];
+  return compact ? date.slice(5) : WEEKDAYS[parsed.getDay()];
 }

Then pass period === "custom" through buildTrend when building each TrendDay.

Also applies to: 120-132

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop-tauri/src/components/TotalApiValueCard.tsx` around lines 99 -
103, Update dayLabel to accept whether the range is custom and return a
month/day label for custom periods, while preserving weekday labels for other
periods and the existing invalid-date fallback. Pass period === "custom" from
buildTrend when constructing each TrendDay so custom-range axis labels
distinguish dates across multiple weeks.

Comment on lines +243 to 249
if (!model && loading) {
return (
<section className="api-value-card" aria-label="Total API value">
<p className="api-value-card__status">Reading local usage…</p>
</section>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Body still collapses on every reload, so the ring layout isn't preserved.

The early return at Line 243 correctly limits the bare status to the first load, but Line 355 replaces the whole body (ring, legend, trend) with a single paragraph on every subsequent loading pass — switching periods or editing a date collapses the card and then re-expands it. If the intent is to keep the layout while refreshing, render safeModel with a dimmed/aria-busy wrapper instead of swapping the body out. This also makes the Line 243 guard redundant.

Also applies to: 355-359

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop-tauri/src/components/TotalApiValueCard.tsx` around lines 243 -
249, Update the loading branch in TotalApiValueCard so subsequent loading states
preserve the existing ring, legend, and trend layout by rendering safeModel
inside a dimmed or aria-busy wrapper instead of replacing the body with a status
paragraph. Remove the now-redundant initial loading guard around the component’s
early return, while retaining an appropriate first-load empty state when no
model exists.

@tsouth89
tsouth89 merged commit a7c5074 into main Jul 28, 2026
11 checks passed
@tsouth89
tsouth89 deleted the fix/api-value-custom-range-data branch July 28, 2026 01:59
@tsouth89 tsouth89 mentioned this pull request Jul 28, 2026
tsouth89 added a commit that referenced this pull request Jul 28, 2026
## Summary

Version bump to **1.5.16** (build 118) for the custom date range fix
already on main via #173.

## Release plan

1. Merge this PR
2. Tag `v1.5.16` on the merge commit
3. Publish signed draft when `release.yml` finishes

Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com>
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.

2 participants