Skip to content

feat(recommendations): apply per-account overrides at read time (#196)#200

Merged
cristim merged 5 commits intofeat/multicloud-web-frontendfrom
feat/issue-196-readtime-overrides
Apr 29, 2026
Merged

feat(recommendations): apply per-account overrides at read time (#196)#200
cristim merged 5 commits intofeat/multicloud-web-frontendfrom
feat/issue-196-readtime-overrides

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented Apr 29, 2026

Closes #196.

Summary

PR #193 wired account_service_overrides into the purchase forms so overrides are visible at commit time. This PR closes the loop on issue #196 by applying the remaining override fields at the read path:

  • enabled = false → drops that account+service's recs from the listing.
  • include_engines / exclude_engines (and the region / type pairs) → drops non-matching rows.
  • coverage → caps the dashboard "potential savings" headline per-account, so the widget reflects the user's intended commitment, not the un-overridden total.

Plus the frontend cache invalidation the issue called for: every override panel mutation (inline payment edit, Reset/delete, Create modal) now triggers a recommendations refetch.

Architectural choice — post-DB filter, not SQL

The issue text suggested extending RecommendationFilter with account-aware predicates. Pushing array-membership joins against account_service_overrides into buildRecommendationFilter's WHERE clause would force a correlated subquery per (account, provider, service, engine, region, type), and the override table is sparse (most accounts have no row). The existing applySuppressions helper already filters post-DB inside Scheduler.ListRecommendations — this PR mirrors that pattern via a new applyAccountOverrides step.

Both filters share the same over-show-vs-under-show degradation: a lookup error logs and passes the un-filtered list through, so a Postgres blip doesn't blank the recommendations page.

Reuse map

  • config.ResolveServiceConfig(global, override) — already existed, was dead code, now load-bearing.
  • ConfigStore.GetServiceConfig / GetAccountServiceOverride — already exposed via the store interface.
  • Scheduler.applySuppressions — pattern precedent for the new filter.
  • frontend/src/recommendations.ts::loadRecommendations — exported and reused as the refresh trigger.
  • MockConfigStore already implements both store methods we needed for tests.

Files

File Change
internal/config/recommendation_overrides.go NEW — shared ResolveAccountConfigsForRecs helper that walks recs once, dedupes per (account, provider, service), and merges global ⊕ override via ResolveServiceConfig.
internal/scheduler/scheduler_overrides.go NEW — Scheduler.applyAccountOverrides filter (engine/region/type include/exclude + enabled=false drop).
internal/scheduler/scheduler.go Wire applyAccountOverrides into ListRecommendations after applySuppressions.
internal/api/handler_dashboard.go Coverage cap on summarizeRecommendationsWithCoverage + helper resolveCoverageByAccountKey.
frontend/src/settings.ts refreshRecommendationsAfterOverrideChange helper, called from the 3 override-mutation callsites (payment edit, Reset, Create modal submit).

Out of scope (deliberate; follow-up issues acknowledged in #196)

Test plan

Backend (go test ./... clean across 3 passes):

Frontend (npm test clean — 1373/1373 pass; npm run build clean across 3 passes):

  • frontend/src/__tests__/settings-accounts.test.ts — 2 new tests asserting the recs refetch fires after an override save and that a refresh failure is swallowed without a user-facing error toast.

Manual UI smoke skipped — local stack not bootstrapped in this session. The frontend test suite covers the wiring; deploy verification will land via the post-push pipelines.

Summary by CodeRabbit

  • New Features

    • Per-account service overrides applied to recommendations and savings; dashboard potential monthly savings scale with account coverage.
    • Recommendations list respects per-account enable/include/exclude rules and skips filtering for items without an account.
  • Bug Fixes / Reliability

    • Recommendation refresh after saving overrides is best-effort: refresh failures are logged but won’t block saves or show error to users.
  • Tests

    • Expanded frontend and backend tests covering overrides, scaling math, filtering, and refresh behavior.

PR #193 wired account_service_overrides into the purchase forms so
overrides are visible at commit time. This change closes the loop on
issue #196 by applying the remaining override fields at the read path:

- enabled=false hides the account+service's recs from the listing
- include/exclude engines, regions, types drop non-matching rows
- coverage caps the dashboard "potential savings" headline per-account

Filtering is post-DB inside Scheduler.ListRecommendations, mirroring
the precedent set by applySuppressions; pushing array-membership joins
into the WHERE clause was rejected as messier than the in-Go pass and
the override table is sparse. Lookup errors are non-fatal — they log
and pass the un-filtered list through (over-show vs. under-show).

Coverage scaling lives in handler_dashboard.go's aggregator and assumes
recs are generated at 100% coverage; the follow-up dashboard-accuracy
issue referenced from #196 will revisit if rec generation becomes
coverage-aware.

Frontend: every account_service_overrides mutation (inline payment
edit, Reset/delete, Create modal) now triggers a recommendations
refetch via a tiny refreshRecommendationsAfterOverrideChange helper.
The refresh is best-effort: a network blip on the secondary fetch
console.warns and yields to the next nav-driven reload rather than
surfacing as an error toast for a save that already succeeded.

Tests:
- internal/config/recommendation_overrides_test.go covers the shared
  resolver helper (deduping, missing-global, error propagation).
- internal/scheduler/scheduler_overrides_test.go covers the per-account
  filter (enabled=false, include/exclude, nil-account passthrough,
  engine="" lax rule, lookup-error passthrough) plus the issue #196
  acceptance criterion mirrored from #111.
- internal/api/handler_dashboard_test.go covers the coverage cap
  (handler integration + table-driven scaledSavings unit tests).
- frontend/src/__tests__/settings-accounts.test.ts asserts the recs
  refetch fires after an override save and that a refresh failure is
  swallowed without a user-facing error.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@cristim has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 53 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37b49aaa-7a8d-46ce-8b69-e202967e2a25

📥 Commits

Reviewing files that changed from the base of the PR and between 63fdd11 and e8ce3fe.

📒 Files selected for processing (3)
  • internal/api/handler_dashboard_test.go
  • internal/database/postgres/migrations/000043_recommendations_add_engine_to_key.down.sql
  • internal/database/postgres/migrations/000043_recommendations_add_engine_to_key.up.sql
📝 Walkthrough

Walkthrough

Backend and frontend changes wire per-account service overrides into the recommendations read path and dashboard aggregation. Backend resolves per-account configs, filters recommendations at list-time, and scales dashboard savings by coverage; frontend refreshes recommendations after override edits, handling refresh failures as best-effort.

Changes

Cohort / File(s) Summary
Frontend Override Invalidation
frontend/src/settings.ts, frontend/src/__tests__/settings-accounts.test.ts
Adds a post-mutation hook refreshRecommendationsAfterOverrideChange() to call loadRecommendations() (errors ignored) and tests for successful refresh and graceful failure handling.
Recommendation Config Resolution
internal/config/recommendation_overrides.go, internal/config/recommendation_overrides_test.go
New resolver ResolveAccountConfigsForRecs and AccountConfigReader interface to deduplicate lookups, cache global service config per (provider,service), merge per-account overrides, and return a keyed map; tests cover caching, negative caching, and error propagation.
Scheduler Override Application
internal/scheduler/scheduler_overrides.go, internal/scheduler/scheduler_overrides_test.go
Implements applyAccountOverrides (and helpers) to filter recs by per-account resolved configs: Enabled, include/exclude engines/regions/types, with special handling for empty engine and pass-through for recs without CloudAccountID; extensive tests including error-tolerance behavior.
Scheduler Pipeline Integration
internal/scheduler/scheduler.go, internal/scheduler/scheduler_suppressions_test.go
Integrates override application into ListRecommendations after suppressions; on resolver errors it logs and returns unfiltered recs. Added stub methods to suppression tests to avoid interference.
Dashboard Savings Scaling
internal/api/handler_dashboard.go, internal/api/handler_dashboard_test.go
Adds summarizeRecommendationsWithCoverage and resolves per-account coverage via resolveCoverageByAccountKey; scales each recommendation’s potential savings by clamped coverage% when a CloudAccountID and coverage entry exist; tests verify scaling, capping, nil fallbacks, and aggregated per-service totals.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Scheduler
    participant ConfigStore
    participant FilterLogic

    Client->>Scheduler: ListRecommendations(ctx, filter)
    Scheduler->>Scheduler: Read cached recommendations
    Scheduler->>Scheduler: Apply suppressions
    Scheduler->>ConfigStore: ResolveAccountConfigsForRecs(recs)
    ConfigStore->>ConfigStore: Deduplicate (account,provider,service) keys
    ConfigStore->>ConfigStore: Fetch global ServiceConfig (cached)
    ConfigStore->>ConfigStore: Fetch per-account overrides
    ConfigStore-->>Scheduler: Resolved config map (or error)
    Scheduler->>FilterLogic: applyAccountOverrides(recs, configs)
    FilterLogic->>FilterLogic: Filter by Enabled, engines, regions, types
    FilterLogic-->>Scheduler: Filtered recs
    Scheduler-->>Client: Return recommendations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Poem

🐰
I hopped through configs, keys in paw,
I nudged recommendations—now they saw
per-account rules, coverage, and more;
Savings scaled, recs pruned, caches sunned—
A happy rabbit hops, job done! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.95% 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 specifically describes the main change: applying per-account overrides at read time for issue #196.
Linked Issues check ✅ Passed All core requirements from issue #196 are implemented: per-account override field consumption at read-time (enabled, coverage, include/exclude filters), frontend cache invalidation on override changes, and integration tests validating override behavior.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue #196 read-time filtering scope; collection-time filtering and SDK changes remain appropriately out of scope.

✏️ 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-196-readtime-overrides

Review rate limit: 0/5 reviews remaining, refill in 2 minutes and 53 seconds.

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

@cristim cristim added type/feat New capability priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/m Days triaged Item has been triaged labels Apr 29, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 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: 1

🧹 Nitpick comments (2)
frontend/src/__tests__/settings-accounts.test.ts (1)

617-633: Restore console.warn even when the test fails.

If an assertion throws before Line 633, the spy leaks into later tests and makes failures noisier to debug.

Suggested cleanup
-    const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
-
-    const panel = await openOverridesPanel('acc-1');
-    const select = panel.querySelector('select.override-payment-select') as HTMLSelectElement;
-    select.value = 'all-upfront';
-    select.dispatchEvent(new Event('change'));
-    await new Promise(r => setTimeout(r, 0));
-
-    expect(api.saveAccountServiceOverride).toHaveBeenCalledTimes(1);
-    expect(consoleWarnSpy).toHaveBeenCalled();
-    // No error toast should have been shown for the refresh failure: the
-    // success toast from the save path is what the user sees.
-    const toastCalls = mockShowToast.mock.calls.map(c => c[0]);
-    const errorToasts = toastCalls.filter(t => (t as { kind?: string }).kind === 'error');
-    expect(errorToasts).toHaveLength(0);
-
-    consoleWarnSpy.mockRestore();
+    const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
+    try {
+      const panel = await openOverridesPanel('acc-1');
+      const select = panel.querySelector('select.override-payment-select') as HTMLSelectElement;
+      select.value = 'all-upfront';
+      select.dispatchEvent(new Event('change'));
+      await new Promise(r => setTimeout(r, 0));
+
+      expect(api.saveAccountServiceOverride).toHaveBeenCalledTimes(1);
+      expect(consoleWarnSpy).toHaveBeenCalled();
+      // No error toast should have been shown for the refresh failure: the
+      // success toast from the save path is what the user sees.
+      const toastCalls = mockShowToast.mock.calls.map(c => c[0]);
+      const errorToasts = toastCalls.filter(t => (t as { kind?: string }).kind === 'error');
+      expect(errorToasts).toHaveLength(0);
+    } finally {
+      consoleWarnSpy.mockRestore();
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/__tests__/settings-accounts.test.ts` around lines 617 - 633, The
test spies on console.warn using consoleWarnSpy (jest.spyOn(console, 'warn'))
but only calls consoleWarnSpy.mockRestore() at the end, which leaks the spy if
an assertion throws; wrap the test logic that sets up consoleWarnSpy, calls
openOverridesPanel/select/change, and the subsequent expects in a try/finally so
consoleWarnSpy.mockRestore() is executed in the finally block (or move the
restore into an afterEach for the spec) to guarantee restoration even on
failures.
internal/scheduler/scheduler_overrides_test.go (1)

248-261: Add explicit override-lookup error pass-through test.

You already verify pass-through for global lookup failure; add the sibling case for GetAccountServiceOverride errors to lock in the same contract.

🧪 Suggested test addition
+func TestApplyAccountOverrides_OverrideLookupError_PassesThrough(t *testing.T) {
+	ctx := context.Background()
+	store := &mockOverrideStore{
+		recs: []config.RecommendationRecord{
+			rdsRec("acct-A", "us-east-1", "mysql"),
+		},
+		globals: map[string]*config.ServiceConfig{
+			"aws|rds": {Provider: "aws", Service: "rds", Enabled: true},
+		},
+		getOverrideErr: errors.New("override lookup timeout"),
+	}
+	s := &Scheduler{config: store}
+
+	recs, err := s.ListRecommendations(ctx, config.RecommendationFilter{})
+	require.NoError(t, err, "ListRecommendations should swallow override resolver errors")
+	assert.Len(t, recs, 1, "unfiltered list returned on override lookup failure")
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/scheduler/scheduler_overrides_test.go` around lines 248 - 261, Add a
sibling test mirroring TestApplyAccountOverrides_LookupError_PassesThrough that
asserts account-override lookup errors are passed through: instantiate
mockOverrideStore with getAccountErr set to a non-nil error (instead of
getGlobalErr), include a sample recommendation in recs, create Scheduler{config:
store}, call s.ListRecommendations(ctx, config.RecommendationFilter{}), and
assert no error from ListRecommendations and that the unfiltered recommendation
list is returned; reference mockOverrideStore, getAccountErr,
GetAccountServiceOverride, Scheduler and ListRecommendations when locating where
to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/api/handler_dashboard.go`:
- Around line 149-163: The scaledSavings function incorrectly treats an explicit
coverage==0 as "no override" because of the `if !ok || coverage <= 0 { return
rec.Savings }` check; change the logic so only a missing entry (`!ok`) returns
rec.Savings, then clamp coverage into [0,100] (e.g., coverage =
min(max(coverage, 0), 100)) and apply the scale; keep the fast path `if coverage
>= 100 { return rec.Savings }` if desired, otherwise return `rec.Savings *
coverage / 100`. Reference: scaledSavings and config.AccountConfigKey to find
the coverage lookup.

---

Nitpick comments:
In `@frontend/src/__tests__/settings-accounts.test.ts`:
- Around line 617-633: The test spies on console.warn using consoleWarnSpy
(jest.spyOn(console, 'warn')) but only calls consoleWarnSpy.mockRestore() at the
end, which leaks the spy if an assertion throws; wrap the test logic that sets
up consoleWarnSpy, calls openOverridesPanel/select/change, and the subsequent
expects in a try/finally so consoleWarnSpy.mockRestore() is executed in the
finally block (or move the restore into an afterEach for the spec) to guarantee
restoration even on failures.

In `@internal/scheduler/scheduler_overrides_test.go`:
- Around line 248-261: Add a sibling test mirroring
TestApplyAccountOverrides_LookupError_PassesThrough that asserts
account-override lookup errors are passed through: instantiate mockOverrideStore
with getAccountErr set to a non-nil error (instead of getGlobalErr), include a
sample recommendation in recs, create Scheduler{config: store}, call
s.ListRecommendations(ctx, config.RecommendationFilter{}), and assert no error
from ListRecommendations and that the unfiltered recommendation list is
returned; reference mockOverrideStore, getAccountErr, GetAccountServiceOverride,
Scheduler and ListRecommendations when locating where to add the test.
🪄 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: 676870fe-3e79-4af0-9009-2b9f2c167f97

📥 Commits

Reviewing files that changed from the base of the PR and between 7267600 and 4545194.

📒 Files selected for processing (10)
  • frontend/src/__tests__/settings-accounts.test.ts
  • frontend/src/settings.ts
  • internal/api/handler_dashboard.go
  • internal/api/handler_dashboard_test.go
  • internal/config/recommendation_overrides.go
  • internal/config/recommendation_overrides_test.go
  • internal/scheduler/scheduler.go
  • internal/scheduler/scheduler_overrides.go
  • internal/scheduler/scheduler_overrides_test.go
  • internal/scheduler/scheduler_suppressions_test.go

Comment thread internal/api/handler_dashboard.go Outdated
cristim and others added 2 commits April 29, 2026 21:40
… as a real cap.


Right now Line 157 collapses an explicit zero coverage override into "full savings". That breaks the cap semantics: 0 should contribute 0, not 100%. It also contradicts the function comment's min(coverage, 100) / 100 rule, and the UI already accepts 0..100 as valid input.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Harden the console.warn spy cleanup in the frontend override refresh
test, add the missing override-lookup error pass-through regression, and
align the dashboard coverage test with explicit zero-coverage semantics.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

Addressed the latest CodeRabbit pass in commit 63fdd11:\n\n- wrapped the console.warn spy restore in a finally block\n- added override-lookup error pass-through coverage\n- aligned the dashboard zero-coverage test with the explicit 0% cap behavior\n\n@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

@cristim: Thanks for the follow-up! I'll kick off a new incremental review covering commit 63fdd11d7 and the changes you described:

  • finally-block guarding the console.warn spy restore
  • Error pass-through coverage for override-lookup failures
  • Dashboard zero-coverage test alignment with the explicit 0% cap
✅ 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: 1

🧹 Nitpick comments (1)
internal/api/handler_dashboard_test.go (1)

91-93: Use config.AccountConfigKey in the test double to avoid key-format drift.

This keeps test key construction aligned with production lookup behavior in one place.

Proposed refactor
 func (s *dashboardOverrideStore) GetAccountServiceOverride(_ context.Context, accountID, provider, service string) (*config.AccountServiceOverride, error) {
-	return s.overrides[accountID+"|"+provider+"|"+service], nil
+	return s.overrides[config.AccountConfigKey(accountID, provider, service)], nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/handler_dashboard_test.go` around lines 91 - 93, The test double
dashboardOverrideStore's GetAccountServiceOverride currently constructs the
lookup key by concatenating accountID+"|"+provider+"|"+service; change it to use
the same key helper used in production (call config.AccountConfigKey(accountID,
provider, service)) for the map lookup, and update any places that populate
s.overrides to use config.AccountConfigKey as well so the test keys and lookup
logic can't drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/api/handler_dashboard.go`:
- Around line 122-125: The comment above the reducer that currently states “recs
with a recorded coverage of zero (treated as 'no scaling configured') all count
at full weight” is stale; update that comment to reflect the actual runtime
behavior where zero coverage is scaled to 0. Locate the comment block
referencing CloudAccountID / "no scaling configured" (near the reducer handling
coverage mapping) and change the text to state that records without a
CloudAccountID count at full weight, records missing from the coverage map count
at full weight, but records with an explicit coverage value of 0 are scaled to 0
(no weight).

---

Nitpick comments:
In `@internal/api/handler_dashboard_test.go`:
- Around line 91-93: The test double dashboardOverrideStore's
GetAccountServiceOverride currently constructs the lookup key by concatenating
accountID+"|"+provider+"|"+service; change it to use the same key helper used in
production (call config.AccountConfigKey(accountID, provider, service)) for the
map lookup, and update any places that populate s.overrides to use
config.AccountConfigKey as well so the test keys and lookup logic can't drift.
🪄 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: 68eb26ec-024c-499c-bf0d-fc71a56bdf8d

📥 Commits

Reviewing files that changed from the base of the PR and between 4545194 and 63fdd11.

📒 Files selected for processing (4)
  • frontend/src/__tests__/settings-accounts.test.ts
  • internal/api/handler_dashboard.go
  • internal/api/handler_dashboard_test.go
  • internal/scheduler/scheduler_overrides_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/tests/settings-accounts.test.ts
  • internal/scheduler/scheduler_overrides_test.go

Comment on lines +122 to +125
// Recs without a CloudAccountID, recs whose triple has no entry in the
// map, and recs with a recorded coverage of zero (treated as "no
// scaling configured") all count at full weight — this matches the
// pre-#196 behaviour for un-configured accounts.
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

Fix stale zero-coverage behavior in the reducer comment.

Line 123–125 says zero coverage is treated as “no scaling configured” (full weight), but Line 160–162 now correctly scales it to 0. Please update the comment to match runtime behavior and avoid regressions.

Proposed doc fix
-// Recs without a CloudAccountID, recs whose triple has no entry in the
-// map, and recs with a recorded coverage of zero (treated as "no
-// scaling configured") all count at full weight — this matches the
-// pre-#196 behaviour for un-configured accounts.
+// Recs without a CloudAccountID and recs whose triple has no entry in the
+// map count at full weight (unconfigured fallback).
+// A recorded coverage of zero contributes zero savings.
📝 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
// Recs without a CloudAccountID, recs whose triple has no entry in the
// map, and recs with a recorded coverage of zero (treated as "no
// scaling configured") all count at full weight — this matches the
// pre-#196 behaviour for un-configured accounts.
// Recs without a CloudAccountID and recs whose triple has no entry in the
// map count at full weight (unconfigured fallback).
// A recorded coverage of zero contributes zero savings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/handler_dashboard.go` around lines 122 - 125, The comment above
the reducer that currently states “recs with a recorded coverage of zero
(treated as 'no scaling configured') all count at full weight” is stale; update
that comment to reflect the actual runtime behavior where zero coverage is
scaled to 0. Locate the comment block referencing CloudAccountID / "no scaling
configured" (near the reducer handling coverage mapping) and change the text to
state that records without a CloudAccountID count at full weight, records
missing from the coverage map count at full weight, but records with an explicit
coverage value of 0 are scaled to 0 (no weight).

Align the dashboard override test double with production key construction so
coverage override tests cannot drift from the resolver lookup format.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

Addressed the latest CodeRabbit nitpick: dashboardOverrideStore now uses config.AccountConfigKey for lookup, and the test fixture seeds the same helper-built key. Verification: go test ./internal/api; pre-commit Go hooks passed on commit. @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

@cristim: Thanks for the quick turnaround! Using config.AccountConfigKey consistently in both dashboardOverrideStore and the test fixture seed is the right call — it eliminates the risk of the two diverging silently if the key format ever changes. Kicking off the incremental review now.

✅ 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.

Move the recommendations engine-key migration to 000043 so the branch no
longer collides with the retry-linkage 000042 migration on the target branch.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

Fixed the failing pre-commit hook: 000042_recommendations_add_engine_to_key is now renumbered to 000043_recommendations_add_engine_to_key, removing the collision with 000042_purchase_executions_retry_linkage on the target branch. Verification: pre-commit run check-migration-conflicts --all-files; go test ./internal/api ./internal/scheduler ./internal/config; commit hook passed the migration conflict check. @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

@cristim: Good catch on the migration sequence collision — renumbering to 000043 is the correct resolution. Collision with 000042_purchase_executions_retry_linkage on the target branch would have caused issues at apply time, so getting that sorted before merge is the right call. Kicking off the incremental review now.

✅ 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.

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

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/feat New capability urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant