Skip to content

fix(hubs): use max() when duplicate on-demand price rows collapse under one lookup key#2189

Merged
RolandKrummenacher merged 4 commits into
devfrom
fix/reservation-price-min-to-max
Jun 24, 2026
Merged

fix(hubs): use max() when duplicate on-demand price rows collapse under one lookup key#2189
RolandKrummenacher merged 4 commits into
devfrom
fix/reservation-price-min-to-max

Conversation

@RolandKrummenacher

@RolandKrummenacher RolandKrummenacher commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

🛠️ Summary

General robustness fix for the FinOps hubs reservation price backfill. When duplicate on-demand price rows collapse under a single reservation price lookup key, prefer the highest price as the savings baseline instead of the lowest.

Note

This is a general hardening, not a complete fix for #2176 (MCA underreported savings). See "Scope / relationship to #2176" below.

🐛 Behavior change

Missing on-demand prices are backfilled via a leftouter join against the Prices table, aggregated by the reservation price lookup key (x_BillingProfileId + YYYY-MM + x_SkuMeterId + x_SkuOfferId). When multiple Consumption price rows collapse under one key and differ only in price, min() selects the lowest row — which can be an anomalously low value — deflating the on-demand baseline and understating commitment discount savings.

Switching the ListUnitPrice / ContractedUnitPrice aggregation from min() to max() selects the genuine on-demand price in those collision cases.

  • IngestionSetup_v1_2.kql
  • IngestionSetup_v1_0.kql

For tenants whose price rows carry distinct, populated offer IDs (e.g. EA), the lookup key already separates rows, so each summarize group has a single row and min == max — a no-op. The change therefore only affects genuine collision cases and is safe across billing account types.

🔎 Scope / relationship to #2176

This PR does not close #2176. In the reporter's MCA scenario, the affected price rows carry a blank x_SkuOfferId. The cost-side tmp_MissingPrices gate requires isnotempty(x_SkuOfferId), and the lookup key on both sides includes the offer ID — so blank-offer rows are filtered out before this join and never reach the min()/max() collapse. The MCA fix requires relaxing that offer-ID gate and/or keying on a field populated for MCA; tracked in #2176.

📝 Notes

  • release/ and fabric KQL copies are regenerated at release time and are intentionally not included here (consistent with prior KQL fixes).
  • Changelog entry added under FinOps hubs v15 → Fixed.

🤖 Generated with Claude Code

…rreported savings

When MCA price sheets emit a blank x_SkuOfferId, multiple Consumption price
rows for one meter collapse under the same reservation price lookup key. The
duplicates are identical in every identifier (including SkuPriceId and effective
period), differing only in price, so the collision can't be resolved by refining
the key. min() selected the anomalously low row, deflating the on-demand baseline
and underreporting commitment discount savings (sometimes to zero).

Switch the ListUnitPrice/ContractedUnitPrice aggregation from min() to max() in
both v1_0 and v1_2 ingestion paths. For EA / populated-offer tenants the key
already separates rows, so each group has a single row and min == max -- a no-op,
making the change safe across billing account types.

Fixes #2176

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 06:56
@microsoft-github-policy-service microsoft-github-policy-service Bot added the Needs: Review 👀 PR that is ready to be reviewed label Jun 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates FinOps hubs ingestion KQL to prevent underreported reservation/commitment discount savings when multiple on-demand price rows collapse under the same reservation price lookup key.

Changes:

  • Switched reservation reference price aggregation from min() to max() for ListUnitPrice and ContractedUnitPrice in both v1.0 and v1.2 ingestion paths.
  • Added inline rationale comments near the updated summarize statements.
  • Added a FinOps hubs v15 changelog entry documenting the fix (Issue #2176).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql Uses max() when summarizing on-demand prices by reservation lookup key to avoid deflated savings baselines.
src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_0.kql Mirrors the v1.2 change for the v1.0 ingestion path to keep behavior consistent across supported transformations.
docs-mslearn/toolkit/changelog.md Documents the fix under FinOps hubs v15 → Fixed with a link to #2176.

Comment thread docs-mslearn/toolkit/changelog.md Outdated
Roland Krummenacher and others added 2 commits June 22, 2026 09:59
Describe the general duplicate-key behavior instead of the MCA blank-offer
example, which is filtered out by the isnotempty(x_SkuOfferId) gate earlier in
the block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The min->max change does not resolve the MCA scenario in #2176: blank-offer
cost rows are filtered out by the isnotempty(x_SkuOfferId) gate before this
join, so they never reach the price collapse. Reframe the changelog entry as
general hardening and reference the PR instead of #2176.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@RolandKrummenacher RolandKrummenacher changed the title fix(hubs): use max() for duplicate on-demand price lookup to fix underreported savings fix(hubs): use max() when duplicate on-demand price rows collapse under one lookup key Jun 22, 2026
@flanakin flanakin added this to the v15 milestone Jun 24, 2026
@RolandKrummenacher
RolandKrummenacher merged commit 329ac28 into dev Jun 24, 2026
5 checks passed
@RolandKrummenacher
RolandKrummenacher deleted the fix/reservation-price-min-to-max branch June 24, 2026 15:56
flanakin added a commit that referenced this pull request Jul 16, 2026
Ports #2151 (strcat-free emptiness checks), #2189 (max()-based price
lookup for duplicate on-demand rows), and the AmortizedCost docstring
copy-paste fix into IngestionSetup_v1_4.kql, which was copied from
v1_2 before those fixes landed on dev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs: Review 👀 PR that is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reservation savings underreported due to min() price selection on duplicated reservation price keys

4 participants