You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(frontend/recs): Effective % shows implausible values (85.9% on Azure 1y RI, 54% on savings=$0 row) #274
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:
on_demand_monthly = monthly_cost + savings + amortized assumes rec.savingsexcludes 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 %.
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.
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.
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).
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.
Activity
fix(recommendations): plumb provider on_demand_cost through to fronte…
cristim commentedon May 5, 2026
Resolved by PR #277 (merge commit
66a993fa5, 2026-05-05). The fix plumbsOnDemandCostfrom each provider's API (AWS EstimatedMonthlyOnDemandCost,Azure CostWithNoReservedInstances) throughpkg/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 preferson_demand_costwhen 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 #274in the PR title didn't fire (same pattern as #196 / #208 / #209 / #210 in this repo).cristim commentedon May 13, 2026
Verified resolved by PR #277 (merged 2026-05-05).
Evidence of the complete plumbing path:
pkg/common/types.go::Recommendation.OnDemandCostwas already populated by the Azure/AWS provider SDKs.internal/config/types.go::RecommendationRecordgained a newOnDemandCost *float64field (JSON:on_demand_cost,omitempty), persisted via the existing JSONBpayloadcolumn — no DDL migration required.internal/scheduler/scheduler.go::convertRecommendationsnow writesnonZeroPtr(rec.OnDemandCost)into each record, wherenonZeroPtrreturnsnilfor provider-returned 0 (= "not populated"), preventing the frontend's "is this populated?" branch from being fooled by a literal-zero value.internal/api/types.gousesconfig.RecommendationRecorddirectly, soon_demand_costflows through the JSON response with no additional API type change.frontend/src/api/types.ts::Recommendationgainedon_demand_cost?: number | null.frontend/src/types.ts::LocalRecommendationgained the same field.frontend/src/recommendations.ts::effectiveSavingsPctnow prefers the provider-supplied baseline as the denominator when non-null and non-zero, falling back to the oldmonthly_cost + savings + amortized_upfrontreconstruction for older cached recs. The live repro case (Azure D11_v2 1y all-upfront) drops from ~86% → ~21.9%.TestScheduler_ConvertRecommendations_OnDemandCost(Go) pins the round-trip for populated and zero values. Five new frontend tests in theon_demand_cost preference (#274)sub-describe cover: live repro, override-over-reconstruction, null/undefined fallback, zero-fallback, and rescue of themonthly_cost === nullcase. All 145 frontend tests and the Go scheduler tests pass.Recommending closure.