Skip to content

feat(cli): end-of-run summary of dropped recommendations (closes #361)#875

Open
cristim wants to merge 3 commits into
feat/multicloud-web-frontendfrom
fix/361-wave19
Open

feat(cli): end-of-run summary of dropped recommendations (closes #361)#875
cristim wants to merge 3 commits into
feat/multicloud-web-frontendfrom
fix/361-wave19

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 30, 2026

Summary

Closes #361. Tracks recommendations dropped during sizing, filtering, and family-NU partitioning, then prints a per-stage end-of-run summary so users can see why an instance type didn't make the final plan instead of silently missing.

  • New pkg/common/drop_summary.go + tests — DropSummary collects per-stage counts with optional reasons, formats a multi-line summary at end-of-run.
  • applyFilters, applySizing, and ApplyFamilyNUSizingRDS now thread a *common.DropSummary (nil-safe — production callers can pass nil to opt out).
  • Test call sites updated to pass nil; existing assertions unchanged. Family-NU partition signature gained a 3rd return (dropped count) — both test cases adjusted.

Test plan

  • go test github.com/LeanerCloud/CUDly/cmd/... github.com/LeanerCloud/CUDly/providers/aws/recommendations/... github.com/LeanerCloud/CUDly/pkg/common/... -> 1201 pass across 9 packages
  • Manual: run optimizer with a recommendation set that triggers filter/sizing drops, verify the end-of-run summary shows non-zero counts

Summary by CodeRabbit

Release Notes

New Features

  • Enhanced recommendation filtering with detailed drop tracking showing why recommendations are excluded (e.g., target coverage already met, sizing constraints, policy-based filtering).
  • Drop summaries now display during processing, providing transparency into excluded recommendations.

Review Change Stack

Track and surface recommendations that were dropped during sizing,
filtering, or family-NU partitioning so users see why an instance
type didn't make it into the final plan instead of silently missing.

DropSummary collects per-stage counts with optional reasons; printed
at end-of-run when any drop occurred. applyFilters, applySizing, and
ApplyFamilyNUSizingRDS now thread a *common.DropSummary through their
signatures (nil-safe). Tests cover collection, formatting, and the
no-drops/nil-summary fast paths.
@cristim cristim added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/internal Team-internal only effort/s Hours type/feat New capability labels May 30, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 6 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd9a988b-1322-4195-959b-af4c6b19ccf2

📥 Commits

Reviewing files that changed from the base of the PR and between e9b45f3 and 0386b66.

📒 Files selected for processing (5)
  • cmd/multi_service_helpers.go
  • pkg/common/drop_summary.go
  • pkg/common/drop_summary_test.go
  • providers/aws/recommendations/family_nu.go
  • providers/aws/recommendations/family_nu_test.go
📝 Walkthrough

Walkthrough

This PR adds infrastructure to track and report why recommendations are dropped during recommendation processing. A new DropSummary accumulator records per-category drop counts (min-pool-size, extended-support exclusion, target-met, family-NU sizing, deduplication) across filtering, sizing, and purchasing stages, surfacing an aggregated summary at the end of the run.

Changes

Drop Tracking Feature

Layer / File(s) Summary
Drop Summary infrastructure and constants
pkg/common/drop_summary.go, pkg/common/drop_summary_test.go
Introduces DropSummary type with per-reason drop counters and methods (NewDropSummary, Add, Total, IsEmpty, FormatOneLine). Adds typed constants for drop reasons: DropMinPoolSize, DropExtendedSupport, DropTargetAlreadyMet, DropTargetSizedToZero, DropFamilyAlreadyAtTarget, DropFamilySizedToZero, DropDuplicateDedup. Full test coverage for accumulation, nil-receiver safety, and deterministic formatting.
Target coverage sizing drop tracking
cmd/helpers.go, cmd/helpers_test.go
ApplyTargetCoverage and applySizing now accept drops *common.DropSummary; internal applyTargetCoverageOne returns richer results (adjusted, kept, missingSignal, dropReason). RI logic distinguishes DropTargetAlreadyMet and DropTargetSizedToZero as separate drop categories; Savings Plans pass through when signal is missing; unsupported commitment types warn once per type.
Recommendation filtering drop reporting
cmd/multi_service_filters.go, cmd/multi_service_filters_test.go, cmd/multi_service_test.go
applyFilters now accepts drops and records exclusion reasons; processRecommendation returns (adjusted, include, dropReason) with DropExtendedSupport for engine-version exclusions and DropMinPoolSize for min-pool-size failures. Dimension filtering split into thin wrapper and passesDimensionFiltersWithReason to preserve complexity constraints.
Pipeline integration: wiring drops through recommendation processing
cmd/multi_service_helpers.go
Threading drops through entire pipeline: applyRegionFilters, applyCoverageAndOverrides, checkDuplicatesAndApplyLimit, fetchAndFilterRegionRecs all accept and forward drops. Legacy processRegionRecommendations path passes nil. fetchAllRecs initializes new DropSummary, passes it through all regions/services, and returns both recommendations and populated summary; records family-NU and duplicate-dedup drop counts.
RDS family-NU sizing drop tracking
providers/aws/recommendations/family_nu.go, providers/aws/recommendations/family_nu_test.go
Introduces FamilyDropCounts struct with AlreadyAtTarget and SizedToZero counters. ApplyFamilyNUSizingRDS returns third value tracking families at/above target and families whose scaled results floor to zero. Internal sizeRDSFamilyRecs and scaleFamilyRecs updated to return drop counts.
End-of-run drop summary reporting
cmd/multi_service.go
runToolMultiService now captures DropSummary from fetchAllRecs and prints one-line summary before scoring/purchase phases. CSV path (filterAndAdjustRecommendations) passes nil to skip drop tracking on that code path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • LeanerCloud/CUDly#339: Extends the same target-based RI/SP sizing implementation (ApplyTargetCoverage, ApplyFamilyNUSizingRDS) with per-reason drop tracking and updated return values.

Poem

🐰 A tally of the trodden path
Through pools and families we've chosen not to math,
Each drop now tracked by reason clear,
So ops may know just why recs disappear.
The summary's sung at run's last breath—
No mystery in CUDly's recommendation death!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% 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 summarizes the main change: adding an end-of-run summary of dropped recommendations, and references the related issue #361.
Linked Issues check ✅ Passed The PR implements all specified drop-category accounting from issue #361: --min-pool-size filtering, target-coverage sizing, family-NU partitioning, extended-support exclusions, duplicate-dedup, and sized-to-zero scenarios. DropSummary is threaded through the pipeline and formatted for display.
Out of Scope Changes check ✅ Passed All changes are scoped to drop-accounting instrumentation: new DropSummary type, function signature updates to thread drops through filters/sizing, test updates to pass nil for drop tracking, and family-NU return value changes. No unrelated refactoring or feature additions detected.

✏️ 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 fix/361-wave19

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 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)
providers/aws/recommendations/family_nu.go (1)

256-260: 💤 Low value

Consider the semantic fit of currentNU <= 0AlreadyAtTarget.

When currentNU <= 0, it means the family's recommendations sum to zero NU (typically because instance types aren't in the NU map). Labeling this as "AlreadyAtTarget" is semantically imprecise—it's more "no scalable NU signal" than "target already met."

However, if the drop-summary constants in layer 1 don't provide a distinct category (e.g., DropFamilyNoNUSignal), then grouping with AlreadyAtTarget may be acceptable as "cannot scale to contribute toward target."

Verify that this grouping aligns with the user-facing summary labels.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@providers/aws/recommendations/family_nu.go` around lines 256 - 260, The check
currentNU <= 0 is being classified as drops.AlreadyAtTarget which is
semantically misleading; change the drop categorization so the case where the
family's recommendations sum to zero NU is recorded as a more accurate category
(e.g., DropFamilyNoNUSignal) or, if that constant doesn't exist, add it and use
it instead of AlreadyAtTarget; update any user-facing summary labels/enum in the
layer-1 drop constants (and adjust any callers that consume
drops.AlreadyAtTarget or the new DropFamilyNoNUSignal) to ensure the UI/summary
reflects "no scalable NU signal" rather than "already at target" while keeping
the early-return behavior (return nil, drops) and preserving indices counting.
providers/aws/recommendations/family_nu_test.go (1)

127-363: ⚡ Quick win

Add test coverage for FamilyDropCounts.

All call sites now unpack three return values but discard the third (drops) with _. The new drop-tracking feature is untested at this layer. Consider adding assertions to verify:

  • Line 237 test case ("family at-or-above target"): assert.Equal(t, 1, drops.AlreadyAtTarget)
  • A new test case where scaling produces floor(0) for a rec: assert.Equal(t, 1, drops.SizedToZero)
  • Other test cases: assert.Equal(t, 0, drops.AlreadyAtTarget + drops.SizedToZero) (no drops)

This would catch regressions in drop accounting before it reaches the end-of-run summary layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@providers/aws/recommendations/family_nu_test.go` around lines 127 - 363, The
test suite exercises ApplyFamilyNUSizingRDS but never asserts the new
FamilyDropCounts return (the third return value, commonly named drops); update
the existing tests that call ApplyFamilyNUSizingRDS to capture the third return
and assert its fields: in the "family at-or-above target" case capture drops and
assert drops.AlreadyAtTarget == 1; add a new test case where scaling results in
floor(0) for a rec and assert drops.SizedToZero == 1; for the remaining
positive/unchanged cases (e.g., "no coverage signal", "AWS rec
under-recommends", "AWS rec over-recommends", "RecurringMonthlyCost scales",
"non-RDS recs flow through", "ProjectedCoverage", "multiple sizes") capture
drops and assert drops.AlreadyAtTarget + drops.SizedToZero == 0 so drop
accounting is exercised consistently when calling ApplyFamilyNUSizingRDS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/common/drop_summary.go`:
- Around line 35-40: DropSummary.Add can panic when called on a zero-value
receiver because d.counts may be nil; modify DropSummary.Add (pointer receiver
Add) to lazily initialize d.counts (e.g., if d.counts == nil { d.counts =
make(map[string]int) }) before performing d.counts[reason] += n, keeping the
existing nil receiver and zero n guards.

---

Nitpick comments:
In `@providers/aws/recommendations/family_nu_test.go`:
- Around line 127-363: The test suite exercises ApplyFamilyNUSizingRDS but never
asserts the new FamilyDropCounts return (the third return value, commonly named
drops); update the existing tests that call ApplyFamilyNUSizingRDS to capture
the third return and assert its fields: in the "family at-or-above target" case
capture drops and assert drops.AlreadyAtTarget == 1; add a new test case where
scaling results in floor(0) for a rec and assert drops.SizedToZero == 1; for the
remaining positive/unchanged cases (e.g., "no coverage signal", "AWS rec
under-recommends", "AWS rec over-recommends", "RecurringMonthlyCost scales",
"non-RDS recs flow through", "ProjectedCoverage", "multiple sizes") capture
drops and assert drops.AlreadyAtTarget + drops.SizedToZero == 0 so drop
accounting is exercised consistently when calling ApplyFamilyNUSizingRDS.

In `@providers/aws/recommendations/family_nu.go`:
- Around line 256-260: The check currentNU <= 0 is being classified as
drops.AlreadyAtTarget which is semantically misleading; change the drop
categorization so the case where the family's recommendations sum to zero NU is
recorded as a more accurate category (e.g., DropFamilyNoNUSignal) or, if that
constant doesn't exist, add it and use it instead of AlreadyAtTarget; update any
user-facing summary labels/enum in the layer-1 drop constants (and adjust any
callers that consume drops.AlreadyAtTarget or the new DropFamilyNoNUSignal) to
ensure the UI/summary reflects "no scalable NU signal" rather than "already at
target" while keeping the early-return behavior (return nil, drops) and
preserving indices counting.
🪄 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: 0729b911-0b90-4841-9fbe-363017c50c32

📥 Commits

Reviewing files that changed from the base of the PR and between 4956d66 and e9b45f3.

📒 Files selected for processing (11)
  • cmd/helpers.go
  • cmd/helpers_test.go
  • cmd/multi_service.go
  • cmd/multi_service_filters.go
  • cmd/multi_service_filters_test.go
  • cmd/multi_service_helpers.go
  • cmd/multi_service_test.go
  • pkg/common/drop_summary.go
  • pkg/common/drop_summary_test.go
  • providers/aws/recommendations/family_nu.go
  • providers/aws/recommendations/family_nu_test.go

Comment thread pkg/common/drop_summary.go
… on zero-value

Calling Add on a zero-value DropSummary (var d DropSummary) would panic
with "assignment to entry in nil map" because the counts map is only
populated by NewDropSummary. Lazily initialise the map on first Add so
both construction patterns are safe.

Adds a regression test that exercises a zero-value DropSummary directly.

Addresses CodeRabbit review on PR #875.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

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

…test coverage (#875 CR)

- DropSummary.Add already had the lazy-init nil-map guard; add
  TestDropSummary_ZeroValue_Safe regression test to lock the behaviour
- Add DropFamilyNoNUSignal ("family-nu-no-nu-signal") constant and
  FamilyDropCounts.NoNUSignal field for the case where AWS-rec NU sums
  to zero (unknown/unrecognised sizes) -- distinct from AlreadyAtTarget
- Fix sizeRDSFamilyRecs: currentNU<=0 branch now sets NoNUSignal instead
  of incorrectly reusing AlreadyAtTarget
- Propagate NoNUSignal in ApplyFamilyNUSizingRDS aggregation loop and
  in cmd/multi_service_helpers.go drops recording
- Capture drops in all TestApplyFamilyNUSizingRDS subtests; assert
  AlreadyAtTarget==1 for the "at target" case, SizedToZero==1 for a
  new "floor(0)" test case, and zero total drops for passing-through cases
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

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

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

Labels

effort/s Hours impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/feat New capability urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant