Skip to content

feat(frontend/recs): add Monthly cost + Effective savings % columns#242

Merged
cristim merged 2 commits intofeat/multicloud-web-frontendfrom
feat/issues-220-221-monthly-effective-columns
May 3, 2026
Merged

feat(frontend/recs): add Monthly cost + Effective savings % columns#242
cristim merged 2 commits intofeat/multicloud-web-frontendfrom
feat/issues-220-221-monthly-effective-columns

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 3, 2026

Summary

Test plan

  • npm test in frontend/ passes (1435 tests, 0 failures).
  • npm run build completes successfully.
  • Table header shows "Monthly Cost" and "Effective %" columns after upfront_cost.
  • No-upfront rec row: Upfront = $0, Monthly = $X, Savings = $Y, Effective % positive.
  • All-upfront rec row: Upfront = $X, Monthly = $0, Savings = $Y, Effective % accounting for amortization.
  • Partial-upfront rec row: all four cells populated with correct values.
  • Clicking "Monthly Cost" or "Effective %" header sorts the table.
  • Filter popover opens for both new columns with numeric expression input.
  • Rec where effective savings < 0: Effective % cell has red styling (effective-pct-negative class).
  • Rec where on_demand_monthly = 0: Effective % shows .

Summary by CodeRabbit

  • New Features

    • Added "Monthly Cost" and "Effective %" columns to the recommendations table with sorting and filtering support.
    • "Effective %" renders an em-dash for no-data, uses negative styling for negative percentages.
  • Bug Fixes / Improvements

    • Improved tie-breaking and numeric handling to prevent incorrect matches for undefined percentages.
  • Tests

    • Expanded test coverage for new columns, sorting/filtering, edge cases, rendering, and UI interactions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f54d8c52-9d8d-4cae-8cf3-c1e8626d6562

📥 Commits

Reviewing files that changed from the base of the PR and between d674ea2 and e0ad339.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts
  • frontend/src/state.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/state.ts
  • frontend/src/recommendations.ts

📝 Walkthrough

Walkthrough

Adds two numeric columns to recommendations: "Monthly Cost" and "Effective %". Introduces exported helpers effectiveMonthlySavings and effectiveSavingsPct, wires both columns into sort/filter/state types, updates selection fallback to use amortized savings, and expands tests for computation, rendering, and sorting.

Changes

Recommendations Table: Monthly Cost & Effective Savings % Columns

Layer / File(s) Summary
Type Definitions
frontend/src/state.ts
Expanded RecommendationsSortColumn and RecommendationsColumnId unions to include 'monthly_cost' and 'effective_savings_pct'.
Sortable / Numeric Registry
frontend/src/recommendations.ts
Added monthly_cost and effective_savings_pct to SORTABLE_NUMERIC_COLUMNS, NUMERIC_COLUMNS, and SORT_HEADER_LABELS with friendly labels "Monthly Cost" and "Effective %".
Helper Functions
frontend/src/recommendations.ts
Added exported effectiveMonthlySavings(r: LocalRecommendation): number (amortizes upfront over term with defensive clamping) and `effectiveSavingsPct(r: LocalRecommendation): number
Selection Logic
frontend/src/recommendations.ts
pickBestVariantPerCell fallback now uses effectiveMonthlySavings instead of the prior inline effective calculation.
Cell Value Routing & Filters
frontend/src/recommendations.ts
categoricalCellValue/numericCellValue updated: monthly_cost maps null→0; effective_savings_pct returns effectiveSavingsPct(r) or Number.NaN when undefined to avoid numeric predicate matches.
Rendering
frontend/src/recommendations.ts
Row markup updated to render Monthly Cost and Effective % cells; Effective % applies effective-pct-negative class when <0 and renders em-dash when null.
Tests: Computation & DOM
frontend/src/__tests__/recommendations.test.ts
Updated tests expect 11 sortable headers; added unit tests for effectiveMonthlySavings and effectiveSavingsPct (edge cases: term=0, on_demand_monthly=0 → null, negative results). DOM tests assert headers and cell rendering for no-upfront/partial/all-upfront, em-dash behavior, negative styling, and that clicking monthly_cost/effective_savings_pct sort headers calls setRecommendationsSort.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 New columns hop into view, two bright lights,
Monthly cost and percent, amortized insights.
Helpers sharpen the math, rows line up neat,
Sorts and filters dance — a rabbit's small feat. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding two new columns (Monthly cost and Effective savings %) to the frontend recommendations table.
Linked Issues check ✅ Passed The PR implementation fully addresses both linked issues #220 and #221: it adds the Monthly cost column with sorting/filtering support, adds the Effective savings % column with edge-case handling and negative styling, exports the required helpers, and extends the type system accordingly.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the requirements in issues #220 and #221; no unrelated modifications detected outside the stated objectives.

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

✨ 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 feat/issues-220-221-monthly-effective-columns

Review rate limit: 3/5 reviews remaining, refill in 19 minutes and 42 seconds.

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

@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/s Hours type/feat New capability labels May 3, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 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.

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/recommendations.ts`:
- Around line 133-136: The current transformer sets effective_savings_pct to
Number.NEGATIVE_INFINITY which makes null-like rows sort first; change the row
mapper (effective_savings_pct: (r) => ...) to return null when
effectiveSavingsPct(r) is not a number (i.e., use effectiveSavingsPct(r) ??
null) and update the sorting logic (the comparator used where rows are sorted)
to be null-aware and place nulls last (e.g., treat null as greater than any real
number or explicitly handle null comparisons so null values are considered
larger than any numeric percentage). Ensure references: effective_savings_pct
mapping, effectiveSavingsPct function, and the sort comparator are updated
accordingly.
- Around line 193-199: The helper effectiveSavingsPct is incorrectly clamping a
zero term to 1 year and producing a percentage; update it to return null for
explicit zero-term rows by checking if r.term === 0 (or r.term !== undefined &&
r.term === 0) before computing monthsInTerm, so zero-term returns null and only
undefined/null or positive terms continue to the existing logic that uses
monthsInTerm, amortized, effectiveSavings, and onDemand.

In `@frontend/src/state.ts`:
- Around line 18-19: The typed sort contract (RecommendationsSortColumn) is
missing the new sortable headers 'monthly_cost' and 'effective_savings_pct',
causing a cast in recommendations.ts; update the RecommendationsSortColumn union
to include 'monthly_cost' and 'effective_savings_pct' and ensure any dependent
types (e.g., the sort state/type used by RecommendationsSortState or similar in
recommendations.ts) are updated to use the extended union so the sort state
becomes type-safe and removes the cast.
🪄 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: ca2c373f-4747-4e79-a740-c0e78abb22a8

📥 Commits

Reviewing files that changed from the base of the PR and between c8a78cb and d674ea2.

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

Comment thread frontend/src/recommendations.ts Outdated
Comment thread frontend/src/recommendations.ts
Comment thread frontend/src/state.ts
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

Addressed all 3 actionable CR findings (POSITIVE_INFINITY sentinel, term=0 returns null, RecommendationsSortColumn union extended). @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

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

@cristim: Thanks for addressing all three findings! I'll kick off a fresh incremental review to verify the fixes and check for anything else.

[review]

cristim added 2 commits May 3, 2026 18:55
…loses #220, closes #221)

- Extract effectiveMonthlySavings() from pickBestVariantPerCell inline arrow
  into a named export so the new Effective % column shares the same
  amortization logic (DRY — no divergent copies).
- Add effectiveSavingsPct() helper: amortizes upfront over term, returns
  null when on_demand_monthly=0 to guard against division by zero.
- Monthly Cost column: renders rec.monthly_cost next to Upfront Cost so
  no-upfront / partial-upfront / all-upfront rows tell the complete cost
  story without mental arithmetic.
- Effective % column: amortized savings as a percentage of on-demand
  monthly cost; negative values get class="effective-pct-negative" for
  anomaly visibility; null renders as em-dash.
- Both columns wired into SORTABLE_NUMERIC_COLUMNS, SORT_HEADER_LABELS,
  FILTERABLE_COLUMNS, NUMERIC_COLUMNS, categoricalCellValue, and
  numericCellValue so sort, filter, and popover infrastructure works
  identically to existing numeric columns.
- state.ts: extend RecommendationsColumnId union with monthly_cost and
  effective_savings_pct for compile-time typo safety.
- Tests: 13 new tests covering helper math (all three payment variants,
  term=0, on_demand=0, negative pct, undefined monthly_cost), column
  header presence, cell rendering, sort-header click wiring.
  Updated two existing tests that hard-coded the column count (9→11).
- effectiveSavingsPct: return null for term=0 (issue #221 acceptance
  criteria says zero-term rows should render as em-dash, not compute a
  percentage from a clamped value)
- SORTABLE_NUMERIC_COLUMNS: replace NEGATIVE_INFINITY sentinel with
  POSITIVE_INFINITY so null-pct rows sort to the bottom in ascending
  order (previously they floated to the top, opposite of the comment)
- state.ts RecommendationsSortColumn: extend union with monthly_cost
  and effective_savings_pct so the dataset cast in renderRecommendationsList
  is type-safe rather than a widening escape hatch
- Update effectiveSavingsPct term=0 test to assert null return value
@cristim cristim force-pushed the feat/issues-220-221-monthly-effective-columns branch from e292e01 to e0ad339 Compare May 3, 2026 17:00
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 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.

@cristim cristim merged commit 9a03c9a into feat/multicloud-web-frontend May 3, 2026
6 checks passed
cristim added a commit that referenced this pull request May 3, 2026
… sort-column type union (CR feedback on #242)

- SORTABLE_NUMERIC_COLUMNS now typed as (r) => number | null; effective_savings_pct
  extractor returns effectiveSavingsPct(r) directly (no POSITIVE_INFINITY sentinel)
- sortedRecommendations null-aware: null rows always placed last regardless of
  asc/desc direction (split-and-concat via guard clauses in the comparator)
- effectiveSavingsPct: explicit r.term === 0 guard (in addition to null check)
  instead of falsy !r.term, so the anomaly path is unambiguous
- state.ts RecommendationsSortColumn already includes monthly_cost and
  effective_savings_pct (no cast needed); comment updated to confirm
- Tests: stale "clamps to 12 months" description updated; new test pins
  null-last sort in both asc and desc for effective_savings_pct column
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

Addressed CR's 3 actionable items in commit b297b92:

  1. sort-column type unionstate.ts RecommendationsSortColumn already included monthly_cost and effective_savings_pct from the previous fix pass; the cast at the click handler is unchanged (it reads from dataset which is always string).

  2. null-aware row mapper + sortSORTABLE_NUMERIC_COLUMNS is now typed (r) => number | null; the effective_savings_pct extractor returns effectiveSavingsPct(r) directly (no POSITIVE_INFINITY sentinel). sortedRecommendations is updated with a null-aware comparator that places null rows last regardless of asc/desc direction. New test pins this behavior.

  3. effectiveSavingsPct zero-term guard — changed from falsy !r.term to explicit r.term === 0 || r.term == null so the zero-term anomaly path is unambiguous. Test description updated from the stale 'clamps to 12 months' wording to 'data anomaly — returns null'.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

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

@cristim Thanks for the detailed summary of the fixes! The three changes look well-reasoned:

  1. Sort-column type union — Good that state.ts already covers both new columns from the earlier pass.
  2. Null-aware comparator — Placing null rows last regardless of sort direction is the right UX choice, and typing SORTABLE_NUMERIC_COLUMNS as (r) => number | null keeps the type contract honest without a sentinel value.
  3. Zero-term guard — The explicit r.term === 0 || r.term == null is much clearer than the falsy !r.term check, especially since term could legitimately be 0 as a data anomaly distinct from missing data.

Kicking off 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/s Hours 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