Skip to content

HP-2446: format number as string#98

Merged
SilverFire merged 2 commits intohiqdev:masterfrom
bladeroot:HP-2446
May 7, 2025
Merged

HP-2446: format number as string#98
SilverFire merged 2 commits intohiqdev:masterfrom
bladeroot:HP-2446

Conversation

@bladeroot
Copy link
Copy Markdown
Member

@bladeroot bladeroot commented Apr 22, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Improved precision in price calculations to ensure more accurate charged amounts.
  • Tests
    • Added new tests to verify pricing accuracy for very small usage values.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 22, 2025

"""

Walkthrough

The update modifies the calculation logic in the calculateSum method within the ProgressivePrice class. Specifically, it changes how the quantity and multiplier values are formatted before being used in arithmetic operations. Instead of casting these values directly to strings, they are now formatted as fixed-point decimal strings with 14 decimal places, potentially impacting the precision of the calculations. Additionally, a new unit test was added to verify pricing behavior for very small usage values measured in bps.

Changes

File(s) Change Summary
src/price/ProgressivePrice.php Modified calculateSum to format quantity and multiplier as fixed-point decimals with 14 decimal places before arithmetic
tests/unit/price/ProgressivePriceTest.php Added testProgressivePriceSmallUsage and data provider to test progressive price calculation for very small usage values (bps)

Poem

In the land of numbers, rabbits leap,
Precision sown in decimals deep.
Fourteen places, neat and bright,
Calculations now just right.
Hopping forward, sums align,
With every hop, results refine.
🐇✨
"""

Possibly related PRs

  • HP-1950 Refactored progressive prices #69: Broad refactor of ProgressivePrice and related classes, focusing on thresholds handling and price calculation logic but not numeric formatting precision in calculateSum.

Suggested reviewers

  • SilverFire
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/price/ProgressivePrice.php (1)

107-110: Improved numeric precision in monetary calculations.

The change to format quantity and multiplier values as fixed-point decimal strings with 14 decimal places will help ensure consistent precision in financial calculations. This addresses potential floating-point precision issues that might occur with direct string casting.

However, note that the (string) cast after sprintf is redundant since sprintf already returns a string. While this doesn't affect functionality, it could be simplified:

 $chargedAmount = $price->money()
-                       ->multiply((string)(sprintf('%.14F', $billedUsage->getQuantity())))
-                       ->divide((string)(sprintf('%.14F', $price->multiplier())));
+                       ->multiply(sprintf('%.14F', $billedUsage->getQuantity()))
+                       ->divide(sprintf('%.14F', $price->multiplier()));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9220708 and a4a4ebe.

📒 Files selected for processing (1)
  • src/price/ProgressivePrice.php (1 hunks)

Comment on lines +108 to +109
->multiply((string)(sprintf('%.14F', $billedUsage->getQuantity())))
->divide((string)(sprintf('%.14F', $price->multiplier())));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/unit/price/ProgressivePriceTest.php (3)

228-234: Consider adding assertions for the correct unit

Since this test specifically focuses on very small usage values (bps), it's important to verify that the unit is preserved or converted correctly throughout the calculation process.

        $this->usage = Quantity::bps(6043);
        $usage = $price->calculateUsage($this->usage);
+        // Assert that the unit is preserved (or converted as expected)
+        $this->assertSame('bps', $usage->getUnit()->getName(), 'Usage unit should be preserved');
        $this->assertSame($this->usage->getQuantity(), $usage->getQuantity());

241-254: Enhance test coverage with more edge cases for small usage values

The current data provider only contains one test case. Consider adding more test cases with varying small usage values to thoroughly test the formatting behavior.

    private function progressivePriceProviderSmallUsage(): Generator
    {
        yield 'Simple case' => [
            'thresholds' => [
                ['price' => '10', 'currency' => 'EUR', 'quantity' => '0', 'unit' => 'gbps'],
            ],
            'money' => 0,
            'price' => '1',
            'prepaid' => '0',
            'trace' => [
                '6043bps * 0.00000001 = 0.00',
            ],
        ];
+        
+        yield 'Edge case - ultra small usage' => [
+            'thresholds' => [
+                ['price' => '10', 'currency' => 'EUR', 'quantity' => '0', 'unit' => 'gbps'],
+            ],
+            'money' => 0,
+            'price' => '1',
+            'prepaid' => '0',
+            'trace' => [
+                '1bps * 0.00000001 = 0.00',
+            ],
+        ];
+        
+        yield 'Edge case - slightly higher usage' => [
+            'thresholds' => [
+                ['price' => '10', 'currency' => 'EUR', 'quantity' => '0', 'unit' => 'gbps'],
+            ],
+            'money' => 1, // Adjust expected amount based on actual calculation
+            'price' => '1',
+            'prepaid' => '0',
+            'trace' => [
+                '1000000bps * 0.00000001 = 0.01',
+            ],
+        ];
    }

238-238: Add trace verification for small usage case

The test for small usage doesn't verify the calculation trace, unlike the other test methods. Consider adding trace verification to ensure the calculation steps are correctly recorded.

        $amount = $price->calculateSum($this->usage);
        $this->assertEquals($expectedAmount, $amount->getAmount());
+        
+        // Verify calculation trace 
+        $trace = array_map(fn($trace) => $trace->__toString(), $price->getCalculationTraces());
+        $this->assertSame($inputThresholdsArray['trace'] ?? [], $trace);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4a4ebe and b8a622b.

📒 Files selected for processing (1)
  • tests/unit/price/ProgressivePriceTest.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/unit/price/ProgressivePriceTest.php (2)
src/price/PriceFactory.php (1)
  • createProgressivePrice (95-100)
src/price/ProgressivePrice.php (2)
  • calculateUsage (50-59)
  • calculateSum (84-121)
🪛 GitHub Actions: phpunit-tests
tests/unit/price/ProgressivePriceTest.php

[error] 235-235: PHPUnit test failure: Failed asserting that 0.006043 is identical to 6043 in testProgressivePriceSmallUsage.

Comment on lines +219 to +239
/**
* @dataProvider progressivePriceProviderSmallUsage
*/
public function testProgressivePriceSmallUsage(
array $inputThresholdsArray,
int $expectedAmount,
string $startPrice,
string $prepaid = '0'
): void {
$price = $this->createProgressivePrice(
prepaid: $prepaid,
startPrice: $startPrice,
thresholdsArray: $inputThresholdsArray
);
$this->usage = Quantity::bps(6043);
$usage = $price->calculateUsage($this->usage);
$this->assertSame($this->usage->getQuantity(), $usage->getQuantity());

$amount = $price->calculateSum($this->usage);
$this->assertEquals($expectedAmount, $amount->getAmount());
}
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.

⚠️ Potential issue

Fix the assertion for quantity comparison in testProgressivePriceSmallUsage

The test is failing with the error: "Failed asserting that 0.006043 is identical to 6043 in testProgressivePriceSmallUsage." This indicates that when you retrieve the quantity from the usage object, unit conversion is happening (bps to gbps or another unit).

To fix this, you should compare the unit types as well or use a different assertion method that accounts for unit conversion:

-        $this->assertSame($this->usage->getQuantity(), $usage->getQuantity());
+        // Option 1: Check that the quantities represent the same value
+        $this->assertEquals($this->usage->getQuantity(), $usage->getQuantity(), '', 0.0001);
+        // Option 2: Or ensure they use the same unit before comparison
+        // $this->assertSame($this->usage->getUnit()->getName(), $usage->getUnit()->getName());
+        // $this->assertSame($this->usage->getQuantity(), $usage->getQuantity());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @dataProvider progressivePriceProviderSmallUsage
*/
public function testProgressivePriceSmallUsage(
array $inputThresholdsArray,
int $expectedAmount,
string $startPrice,
string $prepaid = '0'
): void {
$price = $this->createProgressivePrice(
prepaid: $prepaid,
startPrice: $startPrice,
thresholdsArray: $inputThresholdsArray
);
$this->usage = Quantity::bps(6043);
$usage = $price->calculateUsage($this->usage);
$this->assertSame($this->usage->getQuantity(), $usage->getQuantity());
$amount = $price->calculateSum($this->usage);
$this->assertEquals($expectedAmount, $amount->getAmount());
}
/**
* @dataProvider progressivePriceProviderSmallUsage
*/
public function testProgressivePriceSmallUsage(
array $inputThresholdsArray,
int $expectedAmount,
string $startPrice,
string $prepaid = '0'
): void {
$price = $this->createProgressivePrice(
prepaid: $prepaid,
startPrice: $startPrice,
thresholdsArray: $inputThresholdsArray
);
$this->usage = Quantity::bps(6043);
$usage = $price->calculateUsage($this->usage);
// Option 1: Check that the quantities represent the same value
$this->assertEquals($this->usage->getQuantity(), $usage->getQuantity(), '', 0.0001);
// Option 2: Or ensure they use the same unit before comparison
// $this->assertSame($this->usage->getUnit()->getName(), $usage->getUnit()->getName());
// $this->assertSame($this->usage->getQuantity(), $usage->getQuantity());
$amount = $price->calculateSum($this->usage);
$this->assertEquals($expectedAmount, $amount->getAmount());
}
🧰 Tools
🪛 GitHub Actions: phpunit-tests

[error] 235-235: PHPUnit test failure: Failed asserting that 0.006043 is identical to 6043 in testProgressivePriceSmallUsage.

);
$this->usage = Quantity::bps(6043);
$usage = $price->calculateUsage($this->usage);
$this->assertSame($this->usage->getQuantity(), $usage->getQuantity());
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.

💡 Verification agent

🧩 Analysis chain

Fix the assertion error in the pipeline test failure

The pipeline is reporting a failure with: "Failed asserting that 0.006043 is identical to 6043". This suggests that quantity values are being converted between different units during processing.

Let's investigate how the different units are being handled in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for Quantity class implementation to understand unit handling
rg -A 10 -B 5 "class Quantity"

# Search for where unit conversions might happen
rg -A 5 -B 5 "convert|getUnit|getQuantity"

# Search for how bps is defined or used
rg -A 3 "bps|Quantity::bps"

Length of output: 56761


Update ProgressivePriceTest to assert converted usage correctly

In tests/unit/price/ProgressivePriceTest.php at line 235, the assertion is comparing the original bps quantity (6043) against the converted mbps quantity (0.006043), which will always fail. You should assert the converted value (in the price’s prepaid unit) with a small delta for floating-point precision:

• File: tests/unit/price/ProgressivePriceTest.php
Line: 235

Replace:

$this->assertSame(
    $this->usage->getQuantity(),
    $usage->getQuantity()
);

With:

$this->assertEqualsWithDelta(
    // expected: original usage converted into the price’s prepaid unit
    $this->usage->convert($price->getPrepaid()->getUnit())->getQuantity(),
    $usage->getQuantity(),
    1e-7,
    'Usage should be converted to the price’s prepaid unit before assertion'
);

This ensures you’re comparing the correctly converted floating-point value rather than the raw integer and accounts for any minor rounding differences.

🧰 Tools
🪛 GitHub Actions: phpunit-tests

[error] 235-235: PHPUnit test failure: Failed asserting that 0.006043 is identical to 6043 in testProgressivePriceSmallUsage.

@SilverFire SilverFire merged commit 69d9a9e into hiqdev:master May 7, 2025
2 of 5 checks passed
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