Skip to content

feat(cli): size purchases by target RI/SP utilization (--target-utilization, closes #338)#339

Open
cristim wants to merge 8 commits into
feat/multicloud-web-frontendfrom
feat/cli-target-utilization
Open

feat(cli): size purchases by target RI/SP utilization (--target-utilization, closes #338)#339
cristim wants to merge 8 commits into
feat/multicloud-web-frontendfrom
feat/cli-target-utilization

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 12, 2026

Summary

  • Adds a new CLI flag --target-utilization (-u) that sizes RI/SP purchases by projected post-purchase utilization instead of --coverage % of recommendations.
  • Reuses utilization signals already on the wire (AverageNumberOfInstancesUsedPerHour, AverageUtilization, EstimatedAverageUtilization) — no new AWS API call.
  • Emits three new columns on the dry-run/purchase CSV: ProjectedUtilization, ProjectedCoverage, RecommendedUtilization.

Closes #338.

Why a separate sizing mode

--coverage 80 is "buy 80% of each recommendation's instance count." It answers "how much of demand do we cover?" but ignores "how much of what we buy will actually be used?" In bursty/seasonal workloads, --coverage 80 can still over-buy, leaving commitments idle.

--target-utilization 95 is "buy enough that projected utilization stays ≥ 95%." Counter-intuitively, higher target ⇒ fewer commitments bought (the math is used÷bought; raising the target shrinks the bought side). Use it when under-buying-and-spilling-to-on-demand beats over-buying-and-wasting-commitment.

The two modes are mutually exclusive: when --target-utilization is set, --coverage is ignored with an info log.

Math

  • RI: n_target = floor(AverageInstancesUsedPerHour / (target/100)), capped at rec.Count (never exceed AWS's recommended ceiling). Drop with explanatory INFO log when n_target == 0 (target unreachable). Pass through unmodified when avg is zero (no signal); counted in a single end-of-run skip summary.
  • SP: if RecommendedUtilization >= target, no scaling (AWS already projects above target). Else ratio = RecommendedUtilization / target (always < 1); scales SavingsPlanDetails.HourlyCommitment and rec.EstimatedSavingsexactly the fields ApplyCoverage scales on the SP branch, so the two modes stay structurally consistent. Pass through when RecommendedUtilization <= 0 OR HourlyCommitment <= 0 (CE placeholder rows).

Changes

Commit Scope
7d7baf6d1 pkg/common: 4 new fields on Recommendation
bbbc36a24 parser_ri.go + parser_sp.go: populate the new fields
7d83ec948 cmd/main.go: flag + Config field + validation
79cf1a5ad cmd/helpers.go: ApplyTargetUtilization + applySizing
9468b0aaf cmd/multi_service*.go: route both call sites through applySizing
5f1e982e1 CSV writer: 3 new columns
d40001f6a README: flag table + semantics explainer
2bded0b15 Post-impl review: SP edge cases + target=100 test

Test plan

  • go build ./... clean
  • go test ./... — full suite passes (no regressions in 30+ existing packages)
  • cudly --help shows --target-utilization with the inversion-warning copy
  • TestApplyTargetUtilization_RI covers happy path / AWS-ceiling cap / target unreachable drop / no signal pass-through / target met exactly
  • TestApplyTargetUtilization_RI_Target100 covers the AC's "target == 100 → only purchases with perfectly-matched existing usage" boundary
  • TestApplyTargetUtilization_RI_CostScaling — cost fields untouched on RI branch (matches ApplyCoverage)
  • TestApplyTargetUtilization_SP — no-op / scale-down / no-signal
  • TestApplyTargetUtilization_SP_NoSignalGuardsHourlyCommitment=0 + wrong-Details-type edge cases
  • TestApplySizing — routes based on cfg.TargetUtilization > 0
  • TestValidateTargetUtilization — range + both-flags-set behaviour
  • TestParseRIUtilizationSignals + TestParseSavingsPlanDetail_RecommendedUtilization — fall back to zero on nil/unparseable
  • TestWriteMultiServiceCSVReport_UtilizationColumns — header + blank-when-zero formatting

Design notes

  • Two self-review passes (plan: 10 passes against the design; impl: a fresh-eyes Sonnet fork against the actual code) caught: floor-vs-ceil math, missing ProjectedCoverage AC field, wrong parser function for SPs (parseSavingsPlanDetail, not parseRecommendationDetail), wrong enum identifier names, wrong SP cost-field list, missing explanatory log for unreachable target, and two SP edge cases in this branch. All fixed before the push.
  • Frontend visibility of the new columns is out of scope for this PR per the issue's "Out of scope" section. The CSV change is for the CLI's existing CSV output, not the React frontend.

Summary by CodeRabbit

  • New Features

    • Added --target-utilization flag as an alternative sizing option that overrides --coverage when set.
    • CSV reports now include three new utilization metric columns displaying projected and recommended values.
  • Documentation

    • Updated command reference with --target-utilization flag documentation and detailed guidance on flag interactions.

Review Change Stack

cristim added 8 commits May 12, 2026 10:25
Adds four fields to common.Recommendation to carry the utilization data
needed by the upcoming --target-utilization CLI flag:

- AverageInstancesUsedPerHour / RecommendedUtilization — the AWS-returned
  signal (read from the CE response by the recommendations parser in a
  follow-up commit).
- ProjectedUtilization / ProjectedCoverage — populated by the sizing
  step after we pick our own quantity.

Wire-format compatible: all four fields are omitempty so existing
consumers (frontend, CSV writer) ignore them until they're populated.
Populates the utilization fields added to common.Recommendation in the
previous commit:

- parser_ri.go: new parseRIUtilizationSignals helper, called from
  parseRecommendationDetail. Reads AverageNumberOfInstancesUsedPerHour
  and AverageUtilization from the existing CE response — no new API
  call. Nil pointer or parse failure leaves the field at zero (the
  sizing path treats zero as "no signal" and skips).

- parser_sp.go: reads EstimatedAverageUtilization via the existing
  parseOptionalFloat helper and stores it in RecommendedUtilization.
  AverageInstancesUsedPerHour is left at zero for SPs — the field
  isn't applicable; SP sizing uses the utilization value directly.

Tests in parser_ri_test.go and parser_sp_test.go cover both happy paths
and the nil/unparseable fallbacks.
Adds the user-facing CLI surface for issue #338. The flag accepts a
% in [0, 100]; default 0 means "disabled, fall back to --coverage".
Sizing logic lands in a follow-up commit.

- Config.TargetUtilization (cmd/main.go)
- --target-utilization / -u cobra flag with help text spelling out the
  inversion ("higher target = fewer commitments bought")
- Range validation extracted into validateTargetUtilization (keeps
  validateNumericRanges under the gocyclo 10-complexity threshold);
  explicit info-log when both --target-utilization and --coverage are
  set on the same invocation (target wins). Uses cobra's Changed()
  rather than default-comparison so --coverage 80 (which equals the
  default) still triggers the notice.

validateNumericRanges now takes the *cobra.Command for Changed() access;
existing test callers updated to pass nil where the flag-source-detection
branch is irrelevant. New TestValidateTargetUtilization covers the new
range checks and the both-flags-set path.
The core sizing logic for --target-utilization:

- RIs: n_target = floor(AverageInstancesUsedPerHour / (target/100)),
  capped at rec.Count (we never exceed AWS's recommended ceiling).
  Drop with explanatory INFO log when n_target == 0 (target unreachable,
  per the issue AC). Pass through unmodified when avg <= 0 (no signal);
  counted in a single end-of-run WARN summary.

- SPs: when AWS already projects at-or-above target, no scaling. Else
  ratio = RecommendedUtilization / target (< 1); scale exactly the
  fields ApplyCoverage scales on the SP branch
  (SavingsPlanDetails.HourlyCommitment + EstimatedSavings — NOT
  CommitmentCost/OnDemandCost). Discrimination via IsSavingsPlan(rec.Service)
  to match the existing ApplyCoverage convention.

- Routing: new applySizing(recs, cfg, coverage) helper picks between
  ApplyTargetUtilization and ApplyCoverage based on cfg.TargetUtilization.
  The explicit `coverage` parameter lets the CSV path pass its
  csvModeCoverage-substituted value while the main path passes
  cfg.Coverage. Helper-wiring at the call sites lands in the next
  commit.

Per-rec dispatch extracted into applyTargetUtilizationOne to keep
ApplyTargetUtilization under gocyclo's complexity threshold.

Tests cover: RI happy path / AWS-ceiling cap / target unreachable drop /
no signal pass-through / target-met-exactly; SP no-op / scale-down /
no-signal pass-through; cost fields untouched for RIs and partial cost
fields untouched for SPs (so divergence with ApplyCoverage's column set
is impossible without a failing test); applySizing routing logic.
Routes the two sites that previously called applyCommonCoverage directly
through the new applySizing helper, so --target-utilization takes effect
in both pipelines:

- cmd/multi_service.go: CSV-input path. The CSV mode short-circuit
  (skip sizing when csvModeCoverage == 100) is updated to NOT skip
  when target-utilization is set — TargetUtilization applies regardless
  of the coverage value.

- cmd/multi_service_helpers.go: main recommendation-fetch path
  (applyCoverageAndOverrides). Drops the per-mode coverage call in
  favour of applySizing; emits a 🎯 vs 📈 log line based on which
  sizing mode fired.

applyCommonCoverage stays in place (called by applySizing's else
branch) so the legacy coverage path is byte-for-byte unchanged when
--target-utilization is unset.
Adds three columns to the dry-run / purchase CSV emitted by
writeMultiServiceCSVReport:

- ProjectedUtilization — populated by ApplyTargetUtilization on the
  sized rec (RI: avg/n_bought * 100, capped at 100; SP: target or
  AWS's RecommendedUtilization when no scaling needed).
- ProjectedCoverage — RI only (n_bought / avg * 100, capped at 100);
  blank for SPs since the CE response doesn't expose total-demand-$.
- RecommendedUtilization — AWS's projected utilization for the full
  recommendation count (RI AverageUtilization or SP
  EstimatedAverageUtilization).

Zero values render as blank cells via the new formatPercentOrBlank
helper — zero means "unknown / not applicable" (e.g. when running on
the legacy coverage path), and "0.0" cells would mislead.
Adds the new flag to the Purchase Configuration table and spells out
the semantic difference vs `--coverage` plus the inversion
(higher target → fewer commitments). The `--help` copy was added in
the earlier flag-declaration commit and is sufficient at the CLI;
this is for the README's reference table that's the typical first
stop for new users.
Two SP-branch bugs surfaced by post-implementation review of #338:

1. **Details=nil set ProjectedUtilization anyway.** When rec.Details was
   not a *SavingsPlanDetails (a defensive log-and-pass-through path),
   the function still set ProjectedUtilization = targetPct on its way
   out. Result: CSV row showed target utilization while cost fields
   stayed unscaled — misleading the operator. Now returns the rec
   unscaled with zero projection metrics on the Details-type mismatch.

2. **HourlyCommitment=0 with positive utilization signal slipped past
   the no-signal guard.** Cost Explorer occasionally returns SP recs
   with a $0 HourlyCommitment placeholder. The ratio-scaling branch
   then did 0 * ratio = 0 while still claiming target utilization was
   achieved. Added an explicit `HourlyCommitment <= 0` no-signal check
   above the scaling block — pass through, count in skip summary.

Also fills the AC's "target == 100 → only purchases with perfectly-
matched existing usage" test gap (TestApplyTargetUtilization_RI_Target100).
Verifies the avg < 1 drop case, avg=1.0 buy-1 boundary, avg=10.0
perfect-match, and the avg > N truncation-with-clamp case.
@cristim cristim added enhancement New feature or request triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/feat New capability labels May 12, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This PR implements a --target-utilization flag to size commitment purchases (RIs and Savings Plans) so that projected post-purchase utilization stays at or above a user-specified target. It extracts utilization signals from AWS Cost Explorer recommendations, applies proportional sizing logic, validates the flag, integrates into the existing pipeline, and surfaces projected metrics in CSV output.

Changes

Target Utilization Sizing Feature

Layer / File(s) Summary
Data Contracts and Configuration
pkg/common/types.go, cmd/main.go
Recommendation struct gains AverageInstancesUsedPerHour, RecommendedUtilization, ProjectedUtilization, and ProjectedCoverage fields. Config struct adds TargetUtilization field and --target-utilization / -u CLI flag bound to it with help text describing override behavior.
AWS Utilization Signal Parsing
providers/aws/recommendations/parser_ri.go, providers/aws/recommendations/parser_ri_test.go, providers/aws/recommendations/parser_sp.go, providers/aws/recommendations/parser_sp_test.go
RI parser extracts AverageNumberOfInstancesUsedPerHour and AverageUtilization from Cost Explorer recommendation detail; SP parser extracts EstimatedAverageUtilization. Both fall back to zero when fields are nil or unparseable, with warning logs on parse errors. Tests verify signal parsing across valid, nil, and invalid input scenarios.
Target Utilization Sizing Algorithm
cmd/helpers.go, cmd/helpers_test.go
New exported ApplyTargetUtilization function routes per-commitment-type sizing: RI sizing computes capped target instance counts from average utilization and target percentage, drops recommendations when sizing is unreachable, updates projected metrics when kept; SP sizing scales HourlyCommitment and EstimatedSavings proportionally when AWS-projected utilization falls below target. applySizing dispatcher selects ApplyTargetUtilization when cfg.TargetUtilization > 0, else uses coverage-based ApplyCoverage. Six test functions verify RI floor/ceiling clamping, drop behavior, cost-field immutability, SP proportional scaling, mode routing, target=100 boundary, and no-signal guardrails.
Target Utilization Flag Validation
cmd/validators.go, cmd/validators_test.go
validateTargetUtilization enforces 0–100 range for TargetUtilization; logs info when both --target-utilization and --coverage are explicitly set, indicating target utilization takes precedence. Tests cover disabled/boundary/valid/invalid inputs and mutual-set gate.
Recommendation Pipeline Integration
cmd/multi_service.go, cmd/multi_service_helpers.go
filterAndAdjustRecommendations now calls applySizing (instead of applyCommonCoverage alone) when TargetUtilization is set or CSV coverage is not 100%, with conditional logging for target-utilization vs coverage selection. applyCoverageAndOverrides similarly delegates to applySizing and logs sizing mode based on TargetUtilization presence.
CSV Output with Utilization Metrics
cmd/multi_service_csv.go, cmd/multi_service_csv_test.go
CSV header and rows now include ProjectedUtilization, ProjectedCoverage, and RecommendedUtilization columns. Helper function formatPercentOrBlank renders non-zero floats as one-decimal percentages; zero values emit blank cells to avoid "0.0" for unknown metrics. Test validates header presence, decimal formatting, and blank-cell output for zero rows.
User-Facing Documentation
README.md
Expanded --coverage flag description and added --target-utilization flag documentation in Command ReferencePurchase Configuration section, with explanation of counter-intuitive inverse relationship (higher target → fewer purchases) and mutual-exclusion guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A carrot cost cruncher hops into view,
With targets in mind: "Buy less, waste less too!"
Utilization signals dance through the stream—
RIs and Plans sized to match the true dream.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature: adding --target-utilization flag to size RI/SP purchases by target utilization instead of coverage, with issue #338 reference.
Linked Issues check ✅ Passed The PR implements all key objectives from #338: --target-utilization flag with 0-100% validation, RI sizing via floor(avg_used/(target/100)) with AWS cap and drop-when-zero logic, SP sizing via HourlyCommitment ratio scaling, mutual exclusion with --coverage, CSV columns for projected metrics, parser updates for utilization signals, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing target-utilization sizing: utilization fields, parser enhancements, CLI flag/validation, sizing logic, CSV output, routing, documentation, and tests. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-target-utilization

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 12, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 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 (1)
cmd/validators_test.go (1)

417-423: ⚡ Quick win

“Both flags explicitly set” branch is not actually exercised.

Line 453 passes nil cmd, so the cmd.Flags().Changed("coverage") path never runs. This leaves the precedence-log gate untested.

Suggested test adjustment
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"

 	"github.com/LeanerCloud/CUDly/pkg/common"
+	"github.com/spf13/cobra"
 )
@@
-			toolCfg = Config{Coverage: tt.coverage, TargetUtilization: tt.target}
-			err := validateNumericRanges(nil)
+			toolCfg = Config{Coverage: tt.coverage, TargetUtilization: tt.target}
+			cmd := &cobra.Command{Use: "test"}
+			cmd.Flags().Float64("coverage", 80, "")
+			if tt.name == "target + coverage both set, both valid" {
+				if err := cmd.Flags().Set("coverage", fmt.Sprintf("%v", tt.coverage)); err != nil {
+					t.Fatalf("failed to mark coverage as changed: %v", err)
+				}
+			}
+			err := validateNumericRanges(cmd)

Also applies to: 450-454

🤖 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 `@cmd/validators_test.go` around lines 417 - 423, The test
TestValidateTargetUtilization isn't exercising the "both flags explicitly set"
branch because it passes nil for cmd so cmd.Flags().Changed("coverage") never
runs; update the test to create a real cobra.Command (or equivalent command type
used in validators_test.go), register the "coverage" flag, and mark it as set
(e.g., via cmd.Flags().Set("coverage", "<value>") or by calling the appropriate
Changed mechanism) before calling ValidateTargetUtilization so the
cmd.Flags().Changed("coverage") path executes; apply the same fix to the related
case around lines 450-454 to ensure the precedence-log gate is covered.
🤖 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 `@cmd/helpers_test.go`:
- Around line 922-930: The test table includes wantProjCovGTE but the test body
never asserts projected coverage; update the test loop in cmd/helpers_test.go to
check the function result's ProjectedCoverage (or the field name used in the
returned recommendation/result) and assert that if tests[i].wantProjCovGTE > 0
then result.ProjectedCoverage >= tests[i].wantProjCovGTE; do this alongside the
existing projected utilization check so projected coverage regressions are
caught (apply the same addition for the other similar table near lines 992-997
that also declares wantProjCovGTE).

---

Nitpick comments:
In `@cmd/validators_test.go`:
- Around line 417-423: The test TestValidateTargetUtilization isn't exercising
the "both flags explicitly set" branch because it passes nil for cmd so
cmd.Flags().Changed("coverage") never runs; update the test to create a real
cobra.Command (or equivalent command type used in validators_test.go), register
the "coverage" flag, and mark it as set (e.g., via cmd.Flags().Set("coverage",
"<value>") or by calling the appropriate Changed mechanism) before calling
ValidateTargetUtilization so the cmd.Flags().Changed("coverage") path executes;
apply the same fix to the related case around lines 450-454 to ensure the
precedence-log gate is covered.
🪄 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: 00f8f8b7-ce2e-43d4-b506-c5bacddfffd5

📥 Commits

Reviewing files that changed from the base of the PR and between f33ae7c and 2bded0b.

📒 Files selected for processing (15)
  • README.md
  • cmd/helpers.go
  • cmd/helpers_test.go
  • cmd/main.go
  • cmd/multi_service.go
  • cmd/multi_service_csv.go
  • cmd/multi_service_csv_test.go
  • cmd/multi_service_helpers.go
  • cmd/validators.go
  • cmd/validators_test.go
  • pkg/common/types.go
  • providers/aws/recommendations/parser_ri.go
  • providers/aws/recommendations/parser_ri_test.go
  • providers/aws/recommendations/parser_sp.go
  • providers/aws/recommendations/parser_sp_test.go

Comment thread cmd/helpers_test.go
Comment on lines +922 to +930
tests := []struct {
name string
rec common.Recommendation
target float64
wantDropped bool
wantCount int
wantProjUtil float64 // 0 means "don't assert"
wantProjCovGTE float64 // we assert coverage >= this (handles the float clamping)
}{
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 | ⚡ Quick win

wantProjCovGTE is currently dead test data (coverage isn’t actually asserted).

The RI test describes projected coverage validation, but only projected utilization is checked. That leaves ProjectedCoverage regressions untested.

Suggested patch
@@
 	tests := []struct {
 		name           string
 		rec            common.Recommendation
 		target         float64
 		wantDropped    bool
 		wantCount      int
 		wantProjUtil   float64 // 0 means "don't assert"
 		wantProjCovGTE float64 // we assert coverage >= this (handles the float clamping)
 	}{
@@
 			name:         "RI: target 95 hits cap with non-integer avg",
 			rec:          mkRI(10, 8.5, 85),
 			target:       95,
 			wantCount:    8,
 			wantProjUtil: 100,
+			wantProjCovGTE: 94.0,
 		},
@@
 			name:         "RI: target 50 hits AWS ceiling cap",
 			rec:          mkRI(10, 10, 90),
 			target:       50,
 			wantCount:    10,
 			wantProjUtil: 100, // 10/10 = 100
+			wantProjCovGTE: 100.0,
 		},
@@
 			name:         "RI: target 80 hits target exactly",
 			rec:          mkRI(5, 4, 80),
 			target:       80,
 			wantCount:    5,
 			wantProjUtil: 80,
+			wantProjCovGTE: 100.0, // 5/4*100 clamped
 		},
 	}
@@
 			if tt.wantProjUtil > 0 {
 				if math.Abs(out[0].ProjectedUtilization-tt.wantProjUtil) > 0.01 {
 					t.Errorf("ProjectedUtilization: got %.4f, want %.4f",
 						out[0].ProjectedUtilization, tt.wantProjUtil)
 				}
 			}
+			if tt.wantProjCovGTE > 0 {
+				assert.GreaterOrEqual(t, out[0].ProjectedCoverage, tt.wantProjCovGTE)
+			}
 		})
 	}
 }

Also applies to: 992-997

🤖 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 `@cmd/helpers_test.go` around lines 922 - 930, The test table includes
wantProjCovGTE but the test body never asserts projected coverage; update the
test loop in cmd/helpers_test.go to check the function result's
ProjectedCoverage (or the field name used in the returned recommendation/result)
and assert that if tests[i].wantProjCovGTE > 0 then result.ProjectedCoverage >=
tests[i].wantProjCovGTE; do this alongside the existing projected utilization
check so projected coverage regressions are caught (apply the same addition for
the other similar table near lines 992-997 that also declares wantProjCovGTE).

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

Labels

effort/m Days enhancement New feature or request impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant