From 5d5994faebfca8deaa6752cf5f3ab590feb2fea3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 16:49:57 +0000 Subject: [PATCH 1/2] fix: round the cumulative price breakdown like its totals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit computeCumulativeValue formatted each breakdown line at full precision: a quantity normalised from 1000 kWh a year showed as "83.333333 kWh" with an amount of "€28.333333333333", next to a total rounded to the cent. The journey's cumulative price tooltip and the entity price table render these lines as they are. quantityUsed is now rounded to 2 decimals and totalAmountDecimal to 2 decimals like `total`. tierAmountDecimal keeps the tier's real precision, and the *WithPrecision totals are unchanged. Refs STABLE360-13364 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018ZVb2xHqHs2s2bUy7Ut25g --- .changeset/round-cumulative-breakdown.md | 5 ++++ src/tiers/compute-cumulative-value.test.ts | 28 ++++++++++++++++++++-- src/tiers/compute-cumulative-value.ts | 7 +++++- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .changeset/round-cumulative-breakdown.md diff --git a/.changeset/round-cumulative-breakdown.md b/.changeset/round-cumulative-breakdown.md new file mode 100644 index 0000000..7309ee6 --- /dev/null +++ b/.changeset/round-cumulative-breakdown.md @@ -0,0 +1,5 @@ +--- +"@epilot/pricing": patch +--- + +`computeCumulativeValue` rounds its `breakdown` for display like its totals: each tier's `quantityUsed` to 2 decimals and its `totalAmountDecimal` to 2 decimals (`precision: 2`, as `total` is). A quantity normalised from a yearly consumption used to show as `83.333333 kWh` with an amount of `€28.333333333333`. `tierAmountDecimal` keeps the tier's real precision, and `totalWithPrecision` / `subtotalWithPrecision` are unchanged. diff --git a/src/tiers/compute-cumulative-value.test.ts b/src/tiers/compute-cumulative-value.test.ts index fb7f684..0564183 100644 --- a/src/tiers/compute-cumulative-value.test.ts +++ b/src/tiers/compute-cumulative-value.test.ts @@ -72,8 +72,8 @@ describe('computeCumulativeValue', () => { ${onRequestTiers} | ${5} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${true} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€50.00', totalWithPrecision: '€50.00', average: '€10.00/kWh', subAverage: '€8.40/kWh', subtotal: '€42.02', subtotalWithPrecision: '€42.016806722689', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '€10.00/kWh', totalAmountDecimal: '€50.00' }] }} ${onRequestTiers} | ${15} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${true} | ${undefined} | ${{ rate: 19 }} | ${'Price on request'} ${onRequestTiers} | ${15} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€145.00', totalWithPrecision: '€145.00', average: '€9.67/kWh', subAverage: '€8.12/kWh', subtotal: '€121.85', subtotalWithPrecision: '€121.848739495798', breakdown: [{ quantityUsed: '10 kWh', tierAmountDecimal: '€10.00/kWh', totalAmountDecimal: '€100.00' }, { quantityUsed: '5 kWh', tierAmountDecimal: '€9.00/kWh', totalAmountDecimal: '€45.00' }] }} - ${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '38.59 cents', totalWithPrecision: '38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: '32.43 cents', subtotalWithPrecision: '32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.7255 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.866 cents' }] }} - ${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${true} | ${{ rate: 19 }} | ${{ total: 'Starts at 38.59 cents', totalWithPrecision: 'Starts at 38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: 'Starts at 32.43 cents', subtotalWithPrecision: 'Starts at 32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.7255 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.866 cents' }] }} + ${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '38.59 cents', totalWithPrecision: '38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: '32.43 cents', subtotalWithPrecision: '32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.73 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.87 cents' }] }} + ${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${true} | ${{ rate: 19 }} | ${{ total: 'Starts at 38.59 cents', totalWithPrecision: 'Starts at 38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: 'Starts at 32.43 cents', subtotalWithPrecision: 'Starts at 32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.73 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.87 cents' }] }} `( 'should compute cumulative value correctly when quantityToSelectTier=$quantityToSelectTier, isTaxInclusive=$isTaxInclusive and tax=$tax', ({ @@ -105,4 +105,28 @@ describe('computeCumulativeValue', () => { expect(result).toEqual(expected); }, ); + + it('rounds the breakdown of a fractional quantity like the totals', () => { + // 1000 kWh a year, normalised to a monthly price: 83.333… kWh + const result = computeCumulativeValue( + [ + { up_to: 50, unit_amount: 34, unit_amount_decimal: '0.3456' }, + { up_to: null, unit_amount: 30, unit_amount_decimal: '0.30' }, + ], + 1000 / 12, + 'kWh', + 'en', + 'EUR', + mockedTranslationFn, + ); + + expect(result).toMatchObject({ + total: '€27.28', + breakdown: [ + // The unit price keeps its real precision. + { quantityUsed: '50 kWh', tierAmountDecimal: '34.56 cents/kWh', totalAmountDecimal: '€17.28' }, + { quantityUsed: '33.33 kWh', tierAmountDecimal: '30.00 cents/kWh', totalAmountDecimal: '€10.00' }, + ], + }); + }); }); diff --git a/src/tiers/compute-cumulative-value.ts b/src/tiers/compute-cumulative-value.ts index 1acca1c..8d12018 100644 --- a/src/tiers/compute-cumulative-value.ts +++ b/src/tiers/compute-cumulative-value.ts @@ -90,16 +90,21 @@ export const computeCumulativeValue = ( const unitAmountWithTax = isTaxInclusive ? unitAmount : unitAmount.multiply(1 + taxRate); breakdown.push({ + // Rounded for display like the totals: a quantity normalised from a yearly + // consumption is a repeating decimal (1000 / 12 = 83.333…). quantityUsed: `${graduatedQuantity.toLocaleString(formatOptions.locale, { - maximumFractionDigits: 6, + maximumFractionDigits: 2, })} ${formattedUnit}`, tierAmountDecimal: `${formatAmountFromString({ decimalAmount: addSeparatorToDineroString(unitAmountWithTax.getAmount().toString()), ...formatOptions, })}${formattedUnit ? `/${formattedUnit}` : ''}`, + // The tier's unit price keeps its real precision; its amount is rounded like `total`. totalAmountDecimal: formatAmountFromString({ decimalAmount: addSeparatorToDineroString(tierAmountWithTax.getAmount().toString()), ...formatOptions, + precision: 2, + useRealPrecision: false, }), }); From e2a071243d87e9cf8abccee64ccfb017c2afe601 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 17:39:02 +0000 Subject: [PATCH 2/2] chore: release the breakdown rounding as a minor version The breakdown's display strings change, which a consumer could rely on. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018ZVb2xHqHs2s2bUy7Ut25g --- .changeset/round-cumulative-breakdown.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/round-cumulative-breakdown.md b/.changeset/round-cumulative-breakdown.md index 7309ee6..8f09dc0 100644 --- a/.changeset/round-cumulative-breakdown.md +++ b/.changeset/round-cumulative-breakdown.md @@ -1,5 +1,5 @@ --- -"@epilot/pricing": patch +"@epilot/pricing": minor --- `computeCumulativeValue` rounds its `breakdown` for display like its totals: each tier's `quantityUsed` to 2 decimals and its `totalAmountDecimal` to 2 decimals (`precision: 2`, as `total` is). A quantity normalised from a yearly consumption used to show as `83.333333 kWh` with an amount of `€28.333333333333`. `tierAmountDecimal` keeps the tier's real precision, and `totalWithPrecision` / `subtotalWithPrecision` are unchanged.