bug(frontend/recs): Effective % shows implausible values (85.9% on Azure 1y RI, 54% on savings=$0 row) · Issue #274 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

bug(frontend/recs): Effective % shows implausible values (85.9% on Azure 1y RI, 54% on savings=$0 row) #274

Closed

Description

@cristim

Problem

The Effective % column shows implausibly high values for Azure
all-upfront 1-year reservations. From a fresh-deploy refresh on
2026-05-04 (33 recommendations, ${'$'}576/mo summed):

Resource Term Count Savings Upfront Monthly Effective % Plausible %
Azure Standard_D11_v2 (compute, eastus) 1 Year 2 $29 $26 $0 85.9% ~25-35%
Azure Standard_B1ls (compute, mexicocentral) 1 Year 1 $0 $0 $0 54.0% n/a (savings=$0 → should be 0% / em-dash)

Azure 1-year RI savings vs on-demand are typically in the 25-35% range;
the displayed 85.9% is structurally wrong, and the 54.0% on a row with
savings=$0 is even more suspect (the formula should not produce a
positive % from zero savings).

Likely root cause

The formula in
frontend/src/recommendations.ts:402::effectiveSavingsPct is:

amortized_upfront_per_month = upfront_cost / (term * 12)
effective_monthly_savings   = savings - amortized
on_demand_monthly           = monthly_cost + savings + amortized
effective_savings_pct       = effective_monthly_savings / on_demand_monthly * 100

Two suspect points:

  1. on_demand_monthly = monthly_cost + savings + amortized assumes
    rec.savings excludes the upfront amortization (docstring at
    :392-396 makes that explicit). If Azure's API returns savings
    already net-of-amortization, the formula double-counts amortization
    in the denominator → inflated %.

  2. The Azure all-upfront branch typically returns monthly_cost = 0
    (you've paid up-front) plus a savings figure relative to on-demand.
    Plugging in monthly_cost=0, savings=$29, amortized=$2.17
    yields $31.17 on-demand — but the actual on-demand for 2 ×
    Standard_D11_v2 in eastus is ~$50-100/month. The denominator is
    under-estimated. The likely fix is to source on_demand_monthly
    directly from the Azure recommendation payload rather than
    reconstruct it from monthly_cost + savings + amortized.

  3. The Standard_B1ls row with savings=$0 producing 54% strongly
    suggests the underlying record has monthly_cost, upfront_cost,
    and savings all anomalously zero/near-zero, but the percentage is
    coming from somewhere — possibly the formula divides a small
    negative effective_monthly_savings by an even smaller
    on_demand_monthly and produces a positive ratio. Need to repro
    against the raw rec record for that cell.

Acceptance criteria

  • Investigate the actual recommendation record(s) producing 85.9%
    / 54.0% — log-dump or DB SELECT against
    recommendations for id = the affected row and post the raw
    savings, upfront_cost, monthly_cost, term numbers in this
    issue thread before changing code.
  • If the formula's "savings excludes amortization" assumption is
    wrong for Azure, fix the formula (or fork per-provider).
  • If the underlying Azure rec data is wrong (mis-scaled,
    per-instance vs total, stale cache), file a separate
    provider/azure issue and link.
  • Plumb on_demand_monthly (or effective_savings_pct directly)
    from the provider into the persisted record so the frontend formula
    doesn't have to reconstruct it. Mirror the
    confidence_bucket / provenance_note pattern from
    RecommendationDetailResponse.
  • Add an effective-% upper-bound assertion in
    frontend/src/__tests__/recommendations.test.ts
    ("realistic values are always ≤ 60% for 1y, ≤ 75% for 3y; if the
    formula produces > 75%, render ? with a "values may be inaccurate"
    tooltip").

Notes

Related to: #252 (fix(frontend/scheduler): monthly cost shows ${'$'}0 and effective % shows 100%/negatives, PR #254) — the previous round of
fixes addressed similar symptoms but different cells. The current
behaviour is the next layer of the same bug class.

Activity

  1. added a commit that references this issue on May 5, 2026
    66a993f
  2. cristim commented on May 5, 2026

    @cristim
    MemberAuthor

    Resolved by PR #277 (merge commit 66a993fa5, 2026-05-05). The fix plumbs OnDemandCost from each provider's API (AWS EstimatedMonthlyOnDemandCost, Azure CostWithNoReservedInstances) through pkg/common/types.go::Recommendation.OnDemandCost (already populated at the provider layer) → scheduler.convertRecommendations (the missing link — was dropping the field) → RecommendationRecord.OnDemandCost → API JSON → frontend. The frontend formula already prefers on_demand_cost when populated; once the backend stops dropping it, the displayed Effective % matches reality.

    GCP intentionally falls back to the reconstruction formula — the GCP Recommender API doesn't expose a standalone on-demand baseline. Acceptable since GCP recs have well-formed monthly_cost (no all-upfront path).

    Recommend closing manually — the auto-link from closes #274 in the PR title didn't fire (same pattern as #196 / #208 / #209 / #210 in this repo).

  3. cristim commented on May 13, 2026

    @cristim
    MemberAuthor

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

    Evidence of the complete plumbing path:

    • Provider → Go type: pkg/common/types.go::Recommendation.OnDemandCost was already populated by the Azure/AWS provider SDKs. internal/config/types.go::RecommendationRecord gained a new OnDemandCost *float64 field (JSON: on_demand_cost,omitempty), persisted via the existing JSONB payload column — no DDL migration required.
    • Scheduler conversion: internal/scheduler/scheduler.go::convertRecommendations now writes nonZeroPtr(rec.OnDemandCost) into each record, where nonZeroPtr returns nil for provider-returned 0 (= "not populated"), preventing the frontend's "is this populated?" branch from being fooled by a literal-zero value.
    • API contract: internal/api/types.go uses config.RecommendationRecord directly, so on_demand_cost flows through the JSON response with no additional API type change.
    • Frontend API type: frontend/src/api/types.ts::Recommendation gained on_demand_cost?: number | null.
    • Frontend local type: frontend/src/types.ts::LocalRecommendation gained the same field.
    • Formula fix: frontend/src/recommendations.ts::effectiveSavingsPct now prefers the provider-supplied baseline as the denominator when non-null and non-zero, falling back to the old monthly_cost + savings + amortized_upfront reconstruction for older cached recs. The live repro case (Azure D11_v2 1y all-upfront) drops from ~86% → ~21.9%.
    • Tests: TestScheduler_ConvertRecommendations_OnDemandCost (Go) pins the round-trip for populated and zero values. Five new frontend tests in the on_demand_cost preference (#274) sub-describe cover: live repro, override-over-reconstruction, null/undefined fallback, zero-fallback, and rescue of the monthly_cost === null case. All 145 frontend tests and the Go scheduler tests pass.

    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