Skip to content

feat: class based pricing-fetcher with using syntax#49

Merged
ryoppippi merged 4 commits intomainfrom
pricing-fetcher
Jun 9, 2025
Merged

feat: class based pricing-fetcher with using syntax#49
ryoppippi merged 4 commits intomainfrom
pricing-fetcher

Conversation

@ryoppippi
Copy link
Owner

This pull request refactors the cost calculation logic in the src/data-loader.ts, src/debug.ts, and associated test files to improve efficiency and maintainability. The key changes include replacing direct pricing cache manipulation with the PricingFetcher class, converting synchronous cost calculation functions to asynchronous, and ensuring proper cleanup of resources using the using statement.

Refactoring cost calculation logic:

  • Introduction of PricingFetcher class:

    • Replaced direct usage of pricing cache functions (clearPricingCache, fetchModelPricing, getModelPricing) with the PricingFetcher class across src/data-loader.ts and src/debug.ts. This encapsulates pricing logic and simplifies resource management. [1] [2]
  • Conversion to asynchronous cost calculation:

    • Updated calculateCostForEntry to an asynchronous function that uses PricingFetcher for token-based cost calculation. This ensures more flexibility and better handling of asynchronous operations. [1] [2] [3]

Resource management improvements:

  • Using statement for automatic cleanup:
    • Integrated the using statement to manage PricingFetcher instances in functions like loadDailyUsageData, loadSessionData, and detectMismatches. This ensures proper resource cleanup and avoids memory leaks. [1] [2] [3]

Test updates:

  • Refactoring tests to align with new logic:
    • Removed beforeEach blocks that cleared the pricing cache and updated test cases to use PricingFetcher instances for cost calculation. This ensures tests are consistent with the new implementation. [1] [2] [3]

These changes enhance the codebase by improving modularity, reducing direct dependency on pricing cache functions, and ensuring better resource management.

@ryoppippi ryoppippi requested a review from Copilot June 9, 2025 23:09
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Hello @ryoppippi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

Hello team, Gemini here with a summary of this pull request. This PR, authored by @ryoppippi, focuses on refactoring the cost calculation logic within the application. The primary goal is to enhance efficiency and maintainability by introducing a dedicated PricingFetcher class to handle pricing data and calculations. This change moves away from direct manipulation of a global pricing cache. Additionally, functions involved in cost calculation have been updated to be asynchronous, and the new using statement is leveraged for improved resource management, ensuring that PricingFetcher instances are properly cleaned up.

Highlights

  • Refactor: PricingFetcher Class: Replaced direct usage of pricing cache functions (clearPricingCache, fetchModelPricing, getModelPricing) with instances of the new PricingFetcher class across src/data-loader.ts and src/debug.ts. This encapsulates pricing logic and simplifies its usage.
  • Async Cost Calculation: The calculateCostForEntry function has been converted to an asynchronous function. It now accepts a PricingFetcher instance and uses its methods to fetch pricing and calculate costs based on tokens, allowing for better handling of potential asynchronous operations related to pricing data.
  • Resource Management with using: The using statement is now integrated in functions like loadDailyUsageData, loadSessionData, and detectMismatches to manage PricingFetcher instances. This ensures that the fetcher's resources (like the internal cache) are automatically cleaned up when the fetcher goes out of scope.
  • Test Updates: Associated test files (src/data-loader.test.ts, src/debug.test.ts, src/pricing-fetcher.test.ts) have been updated to align with the new class-based, asynchronous approach. This includes removing beforeEach blocks that cleared the old global cache and updating test cases to use PricingFetcher instances.

Changelog

Click here to see the changelog
  • src/data-loader.test.ts
    • Removed beforeEach hooks that cleared the global pricing cache (DIFF 3, DIFF 4, DIFF 5, DIFF 6).
    • Updated imports to use PricingFetcher class instead of standalone functions (DIFF 2).
    • Modified test cases for calculateCostForEntry to be asynchronous and use a PricingFetcher instance (DIFF 6, DIFF 7, DIFF 8, DIFF 9, DIFF 10).
  • src/data-loader.ts
    • Updated imports to use PricingFetcher class (DIFF 1).
    • Changed calculateCostForEntry function signature to be asynchronous and accept a PricingFetcher instance (DIFF 2).
    • Updated logic within calculateCostForEntry to use fetcher.calculateCostFromTokens (DIFF 3).
    • Introduced using fetcher = ... for resource management in loadDailyUsageData (DIFF 4).
    • Updated cost calculation call in loadDailyUsageData to use the fetcher and await (DIFF 5).
    • Introduced using fetcher = ... for resource management in loadSessionData (DIFF 6).
    • Updated cost calculation call in loadSessionData to use the fetcher and await (DIFF 7).
  • src/debug.test.ts
    • Removed beforeEach hook that cleared the global pricing cache (DIFF 1).
    • Updated imports to remove clearPricingCache (DIFF 1).
  • src/debug.ts
    • Updated imports to use PricingFetcher class instead of standalone functions (DIFF 1).
    • Introduced using fetcher = new PricingFetcher() for resource management in detectMismatches (DIFF 2).
    • Updated cost calculation logic within detectMismatches to use fetcher.calculateCostFromTokens and await (DIFF 3).
  • src/pricing-fetcher.test.ts
    • Removed beforeEach hook that cleared the global pricing cache (DIFF 1).
    • Updated imports to use PricingFetcher class and ModelPricing type (DIFF 1).
    • Added tests specifically for the PricingFetcher class, including using statement support and direct cost calculation (DIFF 1).
    • Updated existing tests (fetchModelPricing, getModelPricing, calculateCostFromTokens) to use methods of a PricingFetcher instance instead of standalone functions (DIFF 1, DIFF 2, DIFF 3, DIFF 4, DIFF 5, DIFF 6, DIFF 7, DIFF 8, DIFF 9, DIFF 10).
  • src/pricing-fetcher.ts
    • Refactored standalone pricing functions (clearPricingCache, fetchModelPricing, getModelPricing, calculateCostFromTokens) into a PricingFetcher class (DIFF 1).
    • Implemented the Disposable interface and [Symbol.dispose] method for automatic cleanup (DIFF 1).
    • Changed internal cache and fetchModelPricing return type to Map<string, ModelPricing> for better handling (DIFF 1).
    • Updated getModelPricing and calculateCostFromTokens to be methods of the PricingFetcher class and use the internal cache/logic (DIFF 1).
    • Added a new calculateCostFromPricing method to perform the calculation given tokens and pricing data (DIFF 1).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jun 9, 2025

Open in StackBlitz

npm i https://pkg.pr.new/ryoppippi/ccusage@49

commit: a75f481

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively refactors the cost calculation logic by introducing the PricingFetcher class and leveraging the using statement for resource management. The changes significantly improve code modularity, encapsulation of pricing logic, and maintainability. The conversion of relevant functions to async and the updates to the test suite are well-implemented. The code quality is high, and the PR description is very clear and helpful.

Summary of Findings

  • Code Structure in calculateCostForEntry: In src/data-loader.ts, the calculateCostForEntry function contains an if (mode === 'display') block. Due to the call-site logic in loadDailyUsageData and loadSessionData (where fetcher is null if mode === 'display', and calculateCostForEntry is not called in that case), this specific block is not reached by those primary callers. However, it's correctly maintained for direct calls to calculateCostForEntry (e.g., from tests) and ensures the function adheres to its broader contract. This is a minor observation and doesn't require changes.
  • Test Coverage for getModelPricing Variations: In src/pricing-fetcher.test.ts, a specific test for 'provider prefix' matching in getModelPricing was removed. The new getModelPricing method within the PricingFetcher class handles various model name variations (including prefixes) more broadly. This change is acceptable as the functionality is implicitly covered by other tests that rely on these variations.

Merge Readiness

The pull request is in excellent shape and significantly improves the codebase. I have not found any critical or high-severity issues. The minor observations noted in the findings_summary do not require changes. Based on this review, the code appears ready for merging. As always, ensure any CI checks pass and consider if further review by others is standard practice for your team. I am not authorized to approve pull requests.

Comment on lines 169 to 173
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

It seems like the cost variable is being assigned a value based on whether fetcher is null or not. If fetcher is null, then the cost is assigned data.costUSD ?? 0. If fetcher is not null, then the cost is assigned await calculateCostForEntry(data, mode, fetcher). Is this the intended behavior? If so, consider adding a comment to explain this logic.

Comment on lines 84 to 88
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Since calculateCostForEntry is now asynchronous, it might be beneficial to handle potential errors (e.g., network issues when fetching pricing) more explicitly with a try-catch block to prevent unhandled rejections. What do you think about adding a try-catch block?

try {
		if (mode === 'display') {
			// Always use costUSD, even if undefined
			return data.costUSD ?? 0;
		}
		...
	} catch (error) {
		console.error("Error calculating cost:", error);
		return 0; // Or throw, depending on desired error handling
	}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors cost calculation to use a new PricingFetcher class and the using statement for resource cleanup, converting synchronous cache calls into an async API and updating all callers and tests accordingly.

  • Introduces PricingFetcher class with encapsulated caching and async methods.
  • Converts calculateCostForEntry and related functions to asynchronous, using using for automatic disposal.
  • Updates tests (*.test.ts) to remove direct cache manipulation and leverage PricingFetcher.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/pricing-fetcher.ts Added PricingFetcher class, replaced free functions with methods
src/pricing-fetcher.test.ts Updated tests to instantiate and dispose PricingFetcher via using
src/debug.ts Refactored to use PricingFetcher and using in detectMismatches
src/debug.test.ts Removed cache-clear imports and adjusted imports for PricingFetcher
src/data-loader.ts Made calculateCostForEntry async, applied using in loaders
src/data-loader.test.ts Updated data-loader tests to use async calculateCostForEntry and PricingFetcher
Comments suppressed due to low confidence (5)

src/pricing-fetcher.test.ts:70

  • [nitpick] Relying on a <5ms timing assertion can be flaky on CI or under load. Consider stubbing or mocking network calls, or using a spy to verify that the cache path is taken instead of measuring elapsed time directly.
expect(endTime - startTime).toBeLessThan(5);

src/pricing-fetcher.ts:16

  • The Disposable interface is not imported or defined in this file, which will cause a compilation error. Consider importing Disposable from the appropriate module or removing the implements Disposable clause.
export class PricingFetcher implements Disposable {

src/data-loader.ts:147

  • Declaring fetcher as null in a using statement will invoke disposal on null, causing a runtime error. Split the logic so that using is only applied when new PricingFetcher() is used, or guard disposal when fetcher is null.
using fetcher = mode === 'display' ? null : new PricingFetcher();

src/data-loader.ts:262

  • As above, the using declaration will attempt to dispose null when mode === 'display'. Consider conditionally declaring the using statement only for non-null instances.
using fetcher = mode === 'display' ? null : new PricingFetcher();

src/debug.ts:108

  • The comment indicates skipping comparison when pricing is unavailable, but there is no guard to skip if calculatedCost is zero (pricing missing). Add a conditional (e.g., if (calculatedCost > 0)) around the mismatch calculation to avoid false positives.
// Only compare if we could calculate a cost

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​oxc-project/​runtime@​0.72.31001007395100

View full report

@ryoppippi ryoppippi merged commit c4c9dc5 into main Jun 9, 2025
9 checks passed
@ryoppippi ryoppippi deleted the pricing-fetcher branch June 9, 2025 23:23
ryoppippi added a commit that referenced this pull request Jun 22, 2025
feat: class based pricing-fetcher with using syntax
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