Skip to content

feat(frontend/recs): On-Demand Monthly uses provider on_demand_cost directly (closes #330)#331

Open
cristim wants to merge 2 commits intofeat/multicloud-web-frontendfrom
feat/issue-330-ondemand-api-only
Open

feat(frontend/recs): On-Demand Monthly uses provider on_demand_cost directly (closes #330)#331
cristim wants to merge 2 commits intofeat/multicloud-web-frontendfrom
feat/issue-330-ondemand-api-only

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 7, 2026

Summary

The On-Demand Monthly column on the recommendations table now returns the
provider-supplied r.on_demand_cost directly instead of reconstructing the
value from monthly_cost + savings + (upfront_cost / months_in_term).

Why

PR #322 originally scoped the column to a reconstruction-only helper
(commit comment: "the column's purpose is to display the reconstructed
denominator so users can verify the formula against the raw fields visible
in the same row"
). In practice, post-#277/#312/#324 every supported
provider (AWS, Azure, GCP) plumbs on_demand_cost through to the frontend,
and effectiveSavingsPct already prefers the provider value when present
(its hasOnDemand branch). The two columns therefore disagreed for any row
where the formula reconstruction drifted from the provider's billed price —
Azure all-upfront RIs, AWS Capacity Reservation discounts, partial-day
proration, list-price rounding.

This PR aligns On-Demand Monthly with effectiveSavingsPct's denominator
so the two columns can never disagree.

What changed

  • frontend/src/recommendations.ts::onDemandMonthly simplified to
    return r.on_demand_cost > 0 ? r.on_demand_cost : null — drops
    the arithmetic entirely.
  • Doc comment on onDemandMonthly updated to reflect the new contract.
  • Sort comparator preamble updated — the null sentinel condition is now
    "missing on_demand_cost" rather than "null monthly_cost / term=0".

What stayed

Tests

  • Replaced 7 reconstruction-arithmetic tests in
    describe('onDemandMonthly') with 5 tests covering the new contract:
    provider value used directly, null/undefined/0 return null,
    supporting fields ignored when on_demand_cost is positive.
  • Updated 3 column-rendering tests in describe('Monthly Cost + Effective % column rendering') to set on_demand_cost on the fixture so the
    rendered value matches the new logic.
Test Suites: 42 passed, 42 total
Tests:       1578 passed, 1578 total

Closes #330.

Test plan

  • npx tsc --noEmit clean
  • npm test clean (1578 passed / 0 failed across 42 suites)
  • Manually verify in deployed env: column shows the provider's billed
    on-demand price, not the reconstruction. Em-dash for legacy cached
    rows lacking on_demand_cost.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed inconsistency in "On-Demand Monthly" column calculations to ensure display values match filtering and sorting behavior
    • Column now properly renders provider-supplied costs when available, with em-dashes for missing values
  • Tests

    • Updated test coverage for on-demand cost calculations and column display behavior

…_cost directly (closes #330)

The On-Demand Monthly column now returns the provider-supplied
`r.on_demand_cost` directly instead of reconstructing the value from
`monthly_cost + savings + (upfront_cost / months_in_term)`. When
`on_demand_cost` is missing, undefined, or `0`, the cell renders the
existing em-dash sentinel.

PR #322 originally scoped the column to a *reconstruction-only* helper
("the column's purpose is to display the reconstructed denominator so
users can verify the formula against the raw fields visible in the
same row"). In practice, post-#277/#312/#324 every supported provider
(AWS, Azure, GCP) plumbs `on_demand_cost` through to the frontend,
and `effectiveSavingsPct` already prefers the provider value when
present (its `hasOnDemand` branch). The two columns therefore
disagreed for any row where the formula reconstruction drifted from
the provider's billed price (Azure all-upfront RIs, AWS Capacity
Reservation discounts, partial-day proration, list-price rounding).

The product decision: surface the **authoritative** number from the
provider, not a teaching-tool reconstruction. Users who want to
verify the formula can still compute `monthly_cost + savings +
upfront_cost/(term*12)` from the row's other columns. Aligning this
column with `effectiveSavingsPct`'s denominator means the two never
disagree.

Changes:

* `frontend/src/recommendations.ts::onDemandMonthly` simplified to:
  `return r.on_demand_cost > 0 ? r.on_demand_cost : null` — drops
  the `monthly_cost`/`term`/`upfront_cost` arithmetic entirely.
* Updated the function's doc comment to reflect the new contract
  (the prior comment explicitly said "does NOT use on_demand_cost"
  and is now exactly inverted).
* Updated the `SORTABLE_NUMERIC_COLUMNS.on_demand_monthly`
  preamble — the null-rendering condition is now "missing
  on_demand_cost" rather than "null monthly_cost / term=0".

Tests:

* Replaced the seven reconstruction-arithmetic tests in the
  `describe('onDemandMonthly')` block with five tests covering the
  new contract: provider value used directly, null/undefined/0
  return null, supporting fields ignored when on_demand_cost is
  positive.
* Updated three column-rendering tests in `describe('Monthly Cost +
  Effective % column rendering')` to set `on_demand_cost` on the
  fixture so the rendered value matches the new logic — the
  em-dash and filter regression tests still cover the same
  semantics, just with clearer fixture intent.

`effectiveSavingsPct` keeps its existing reconstruction fallback
unchanged — it's the canonical Effective Savings % calculation and
must stay backward-compatible with legacy cached rows that pre-date
#312/#324.

Verification:
  - `npx tsc --noEmit` clean
  - `npm test` clean (1578 passed / 0 failed across 42 suites)
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/low Minor harm urgency/this-quarter Within the quarter impact/many Affects most users effort/xs Trivial / one-liner type/feat New capability labels May 7, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5860327-831c-45e0-a0bf-b5bc5f715b2e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces onDemandMonthly()'s reconstruction formula with direct return of the provider-reported on_demand_cost field, ensuring consistency with effectiveSavingsPct(). Tests verify the function returns provider values when available and null otherwise, matching column filtering and rendering behavior.

Changes

On-Demand Monthly Provider Alignment

Layer / File(s) Summary
Core Implementation
frontend/src/recommendations.ts
onDemandMonthly() returns r.on_demand_cost directly when > 0, otherwise null; drops the reconstruction formula using monthly_cost + savings + upfront_cost / (term * 12).
Documentation
frontend/src/recommendations.ts
SORTABLE_NUMERIC_COLUMNS comment clarified to note on_demand_monthly uses null sentinel for consistent sorting and filtering.
Unit Tests
frontend/src/__tests__/recommendations.test.ts
Tests assert function returns provider on_demand_cost when populated, and null for null/undefined/zero; reconstruction-arithmetic tests removed.
Column Rendering & Filter Tests
frontend/src/__tests__/recommendations.test.ts
Column renders $150 using provider value when present, em-dash when on_demand_cost is missing, and numeric filter 0 does not match rows with null on_demand_monthly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • #330: This PR directly implements the acceptance criteria for changing onDemandMonthly() to return provider-supplied on_demand_cost instead of a reconstructed value, aligning the column with effectiveSavingsPct() calculations.

Possibly related PRs

  • LeanerCloud/CUDly#312: Plumbs on_demand_cost from AWS/Azure/GCP to the frontend, providing the source data this PR now consumes directly.
  • LeanerCloud/CUDly#322: Introduced the original onDemandMonthly() reconstruction logic that this PR replaces.
  • LeanerCloud/CUDly#242: Introduced effectiveSavingsPct() and the on-demand reconstruction pattern this PR now aligns with by preferring provider values.

Suggested labels

effort/s

Poem

🐰 The on-demand rabbit hops with glee,
No more reconstruction—just provider's decree!
The numbers align from source to display,
Savings percentages and costs agree today. ✨

🚥 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 clearly summarizes the main change: the On-Demand Monthly column now uses the provider's on_demand_cost directly instead of reconstructing it, and it references the closed issue #330.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from #330: returns r.on_demand_cost when > 0, null otherwise, column renders em-dash, sort/filter semantics unchanged, tests updated, no regressions.
Out of Scope Changes check ✅ Passed All changes are scoped to the linked issue: onDemandMonthly function behavior, doc comments, test updates, and no backend changes. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-330-ondemand-api-only

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

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (1)
frontend/src/recommendations.ts (1)

1085-1088: 💤 Low value

Optional: stale parenthetical in comment.

The parenthetical "(null monthly_cost or term=0)" at line 1085 described the old reconstruction logic's null conditions. With the new onDemandMonthly implementation, null is returned when on_demand_cost is missing, null, or 0—independent of monthly_cost or term.

Consider updating for accuracy:

📝 Suggested comment update
-    // Return NaN for null on_demand_monthly (null monthly_cost or term=0) so any
+    // Return NaN for null on_demand_monthly (missing or zero on_demand_cost) so any
     // numeric predicate returns false rather than coincidentally matching 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 `@frontend/src/recommendations.ts` around lines 1085 - 1088, Update the stale
parenthetical in the comment above the on_demand_monthly case to reflect the
current null conditions: mention that onDemandMonthly(r) now returns null when
on_demand_cost is missing, null, or 0 (not "monthly_cost or term=0"), and keep
the rest of the explanation about returning NaN and scaling via
scaleCost(onDemandMonthly(r), period) intact; reference the onDemandMonthly
function and the scaleCost call so reviewers can find the exact code to edit.
🤖 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 `@frontend/src/__tests__/recommendations.test.ts`:
- Around line 3536-3540: The test title claims to cover both "undefined" and
"missing" but only deletes the property; update the test for onDemandMonthly by
additionally creating a record with on_demand_cost explicitly set to undefined
(e.g., const rUndefined = mk({ on_demand_cost: undefined }) ) and asserting
onDemandMonthly(rUndefined) returns null; keep the existing delete-based case
(mk({}) then delete ...) so both missing and explicit undefined are verified for
the onDemandMonthly behavior.

---

Nitpick comments:
In `@frontend/src/recommendations.ts`:
- Around line 1085-1088: Update the stale parenthetical in the comment above the
on_demand_monthly case to reflect the current null conditions: mention that
onDemandMonthly(r) now returns null when on_demand_cost is missing, null, or 0
(not "monthly_cost or term=0"), and keep the rest of the explanation about
returning NaN and scaling via scaleCost(onDemandMonthly(r), period) intact;
reference the onDemandMonthly function and the scaleCost call so reviewers can
find the exact code to edit.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 050082cc-9f11-42be-9f35-726c89f2de1f

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb4e0b and b3fb059.

📒 Files selected for processing (2)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts

Comment on lines 3536 to 3540
test('undefined / missing on_demand_cost returns null', () => {
const r = mk({});
delete (r as { on_demand_cost?: unknown }).on_demand_cost;
expect(onDemandMonthly(r)).toBeNull();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit undefined assertion (current test only proves “missing”).

Line 3536’s test title says “undefined / missing”, but the body only validates the missing-property case via delete. Please add an explicit on_demand_cost: undefined assertion so the contract is fully pinned.

Suggested test tweak
 test('undefined / missing on_demand_cost returns null', () => {
+  expect(onDemandMonthly(mk({ on_demand_cost: undefined }))).toBeNull();
   const r = mk({});
   delete (r as { on_demand_cost?: unknown }).on_demand_cost;
   expect(onDemandMonthly(r)).toBeNull();
 });
📝 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
test('undefined / missing on_demand_cost returns null', () => {
const r = mk({});
delete (r as { on_demand_cost?: unknown }).on_demand_cost;
expect(onDemandMonthly(r)).toBeNull();
});
test('undefined / missing on_demand_cost returns null', () => {
expect(onDemandMonthly(mk({ on_demand_cost: undefined }))).toBeNull();
const r = mk({});
delete (r as { on_demand_cost?: unknown }).on_demand_cost;
expect(onDemandMonthly(r)).toBeNull();
});
🤖 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 `@frontend/src/__tests__/recommendations.test.ts` around lines 3536 - 3540, The
test title claims to cover both "undefined" and "missing" but only deletes the
property; update the test for onDemandMonthly by additionally creating a record
with on_demand_cost explicitly set to undefined (e.g., const rUndefined = mk({
on_demand_cost: undefined }) ) and asserting onDemandMonthly(rUndefined) returns
null; keep the existing delete-based case (mk({}) then delete ...) so both
missing and explicit undefined are verified for the onDemandMonthly behavior.

… (CR pass on PR #331)

Addresses both items from CodeRabbit review 4244506117 on PR #331:

1. **Actionable** — `frontend/src/__tests__/recommendations.test.ts`: the
   single "undefined / missing" test only exercised the
   delete-the-property branch. TypeScript callers can also pass
   `on_demand_cost: undefined` explicitly (strict-null safety pattern),
   and the type system distinguishes "field absent" from "field present
   with value undefined" — so a regression that uses `'on_demand_cost'
   in r` instead of `r.on_demand_cost > 0` would be caught by one but
   not the other. Split into two tests: one for explicit undefined, one
   for the deleted-property case, with comments explaining why both
   matter.

2. **Nitpick** — `frontend/src/recommendations.ts:1085-1088`: the
   parenthetical "(null monthly_cost or term=0)" described the
   pre-#330 reconstruction's null conditions. Refresh to match the
   new contract: "missing or zero on_demand_cost — see onDemandMonthly()
   for the contract". Adds a back-reference so a future reader can find
   the source of truth.

Verification:
  - `npx tsc --noEmit` clean
  - `npm test` clean (1579 passed / 0 failed across 42 suites — +1 test)
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 7, 2026

@coderabbitai — addressed both items from review 4244506117 in commit 6244beb61:

  1. Actionable (test should also cover explicit undefined): split the single "undefined / missing" test into two distinct cases — one for mk({ on_demand_cost: undefined }) and one for the deleted-property path. TypeScript distinguishes "field absent" from "field present with value undefined", and a future regression using 'on_demand_cost' in r instead of r.on_demand_cost > 0 would only be caught by both branches.

  2. Nitpick (stale parenthetical at recommendations.ts:1085): updated "null on_demand_monthly (null monthly_cost or term=0)" to "null on_demand_monthly (missing or zero on_demand_cost — see onDemandMonthly() for the contract)" so the comment matches the new null-condition and back-references the source of truth.

Verification: npx tsc --noEmit clean; npm test clean (1579 passed / 0 failed across 42 suites — +1 test).

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@cristim Thanks for the thorough follow-up! Both fixes look well-reasoned:

  1. Undefined/missing split — the two-branch approach (mk({ on_demand_cost: undefined }) vs. deleted-property) is exactly the right guard here. A future maintainer who rewrites the guard using 'on_demand_cost' in r would only be caught by having both cases present explicitly.

  2. Comment at line 1085 — the updated wording now accurately describes the null condition and back-references onDemandMonthly() as the source of truth, which is much cleaner than the old stale parenthetical.

Triggering a fresh review now.

[review]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/many Affects most users priority/p2 Backlog-worthy severity/low Minor harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant