feat(frontend/recs): add 'On-Demand Monthly Cost' column to recommendations table · Issue #317 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

feat(frontend/recs): add 'On-Demand Monthly Cost' column to recommendations table #317

Closed

Description

@cristim

Summary

Add a new On-Demand Monthly Cost column to the recommendations table on the Recommendations page so users can see the unsubsidised monthly cost alongside the discounted Monthly Cost and Effective % columns.

Why

Today the table shows:

  • Monthly Cost — recurring discounted cost
  • Savings — monthly delta vs on-demand
  • Effective % — savings as a fraction of the equivalent on-demand monthly

…but never surfaces the on-demand baseline directly. Users routinely want to answer "what would I pay without this commitment?" and currently have to do the arithmetic in their head:

on_demand_monthly = monthly_cost + savings + (upfront_cost / (term_years * 12))

This identity is already implemented in frontend/src/recommendations.ts (see the doc comment for effectiveSavingsPct at L388–415) — the value exists, it's just not displayed.

Scope

  • New column key: on_demand_monthly (display label "On-Demand Monthly").
  • Renders formatCurrency(...) for non-null monthly_cost; renders — when monthly_cost is null (provider didn't return a recurring breakdown — same convention as the existing Monthly Cost column at L1510).
  • Sortable + filterable like the other numeric columns:
    • Add to numericColumns near L189–198 (return Number.POSITIVE_INFINITY when null so null rows sort to the end consistently with monthly_cost).
    • Add to the column-header label map at L485-ish.
    • Add to the cell-value resolver around L614–619 (return Number.NaN for null so numeric filter predicates don't match = 0 spuriously).
    • Add to the column orderings at L646 and L1481.
  • Add a unit test covering: null monthly_cost → —, non-null with savings + amortized upfront → matches monthly_cost + savings + (upfront_cost/(term*12)).

Out of scope

  • The cost-period selector (hourly / daily / monthly / yearly) is filed as a separate issue and will fold this column into its scaling once both ship.
  • No backend change required — pure frontend derivation.

Acceptance criteria

  • Column renders correctly for AWS, Azure, GCP rows where monthly_cost is non-null.
  • Renders — (em-dash) when monthly_cost is null.
  • Sort by on_demand_monthly (asc + desc) works; null rows sort to the end.
  • Numeric filter predicates (>, <, =) on the column match the same semantics as monthly_cost.
  • Unit test added in frontend/src/__tests__/recommendations.test.ts.
  • No regressions in existing column ordering / sort / filter tests.

Activity

  1. added 2 commits that reference this issue on May 6, 2026
    6d948b7
    486822c
  2. cristim commented on May 13, 2026

    @cristim
    MemberAuthor

    Verified resolved by PR #322 (merged 2026-05-06).

    Evidence:

    • COLUMN_DEFS / column ID: on_demand_monthly added to the RecommendationsColumnId union in state.ts; SORT_HEADER_LABELS entry 'On-Demand Monthly' added in recommendations.ts.
    • Exported helper onDemandMonthly(): implements monthly_cost + savings + (upfront_cost / (term × 12)); returns null for null monthly_cost or term = 0.
    • Render logic: buildVariantRowMarkup renders formatCurrency(odm) for non-null result, '—' (em-dash U+2014) for null — matching the convention of the existing Monthly Cost column. TABLE_COL_COUNT bumped 13 → 14.
    • Sort support: added to SORTABLE_NUMERIC_COLUMNS with ?? Number.POSITIVE_INFINITY sentinel; extra null-last guard in groupsInSortOrder ensures null rows sort to the end in both asc and desc. Added to both column-ordering arrays (NUMERIC_COLUMNS + COLUMN_ORDER).
    • Filter support: added to FILTERABLE_COLUMNS; numericCellValue returns Number.NaN for null result so numeric predicates (>, <, =) do not spuriously match = 0.
    • Tests (frontend/src/__tests__/recommendations.test.ts, +156 lines): 7 onDemandMonthly() unit tests (null input, term=0, no-upfront, partial-upfront, 3yr, all-upfront, identity formula); rendering tests for non-null ($150) and null (em-dash); filter test confirming null row excluded from = 0 predicate; sort-header-click wiring test; column-count and filter-trigger assertions updated to include on_demand_monthly.

    Recommending closure.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions