fix(hubs): repair ListCost/ContractedCost for rows without a meter/offer ID - #2290
Draft
Michael Flanakin (flanakin) wants to merge 1 commit into
Draft
fix(hubs): repair ListCost/ContractedCost for rows without a meter/offer ID#2290Michael Flanakin (flanakin) wants to merge 1 commit into
Michael Flanakin (flanakin) wants to merge 1 commit into
Conversation
…fer ID The ListCost/ContractedCost repair fallback in IngestionSetup_v1_0.kql and IngestionSetup_v1_2.kql was gated behind the same condition as the price-sheet lookup, which requires a meter ID and offer ID. Rows without either -- most commonly third-party Marketplace/ISV purchases, which have no Microsoft retail list price by design -- were excluded from the whole repair block and never got a repaired ListCost, even with a real EffectiveCost present. That leaves ListCost at 0 despite real spend, collapsing x_TotalSavings and corrupting Effective Savings Rate reporting -- observed as a negative ESR on production hubs (#2214), and reported again independently after an EA-to-MCA migration increased the share of affected rows (#2235). Splits the combined gate: tmp_MissingCost (no meter/offer ID requirement) now gates the cost-repair fallback; tmp_MissingPrices narrows that further to rows the price-sheet join can actually resolve. A row that's tmp_MissingCost but not tmp_MissingPrices skips the join (all joined columns come back null) and falls through the case() blocks' existing null-safe branches to the EffectiveCost-based fallback -- the same outcome #2214 asked for, reusing logic that already exists for the join's no-match case. No new branches were needed in the case() blocks. Also clears the MissingListCost flag in x_SourceChanges once a row's ListCost is known-good post-repair -- previously the flag was computed before the repair ran, so 98.9% of flagged rows on one production hub still reported themselves as broken after being successfully repaired (#2214 defect 4). v1.2 already has x_SourceValues/checkReal provenance tracking, and it picks up newly-repaired rows automatically since old_ListCost is captured before the gate runs -- no new code needed there. v1.0's Costs_final_v1_0 schema is intentionally frozen (kept for reverting to legacy behavior) and does not get x_SourceValues; that remains out of scope permanently, not just for this change. Adds HubsMissingCostGate.Tests.ps1 plus an executable KQL equivalence harness (MissingCostGateSplit.kql, following the ContractedCostTolerance.kql precedent) covering rows missing one/both IDs, unused spend commitments, non-Microsoft providers, and already-correct prices. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
microsoft-github-policy-service
Bot
requested a review
from Brett Wilson (MSBrett)
August 26, 2026 21:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes defects 2 and 4 from #2214 (defect 1 is #2248; defect 3 is out of scope — see below):
IngestionSetup_v1_0.kqlandIngestionSetup_v1_2.kqlgated theListCost/ContractedCostrepair fallback behind the same condition as the price-sheet lookup, which requiresx_SkuMeterId/x_SkuOfferId. Rows without either — most commonly third-party Marketplace/ISV purchases, which have no Microsoft retail list price by design — never reached the repair fallback, leavingListCostat 0 despite a realEffectiveCost. That collapsesx_TotalSavingsand corrupts Effective Savings Rate reporting (negative ESR observed on production hubs in Rows with EffectiveCost but no ListCost produce negative savings rates in the ADX dashboard #2214; reported again independently after an EA→MCA migration in Wrong ESR Calculation after moving from EA to MCA in Rate Optimization #2235).MissingListCostflag doesn't clear after repair. The flag inx_SourceChangeswas computed before the repair ran, so a successfully repaired row still reported itself as broken (98.9% of flagged rows on one production hub, per Rows with EffectiveCost but no ListCost produce negative savings rates in the ADX dashboard #2214's data).How the fix works
Splits the combined gate into two:
tmp_MissingCost(no meter/offer ID requirement) now gates entry into the repair fallback;tmp_MissingPricesnarrows that further to rows the price-sheet join can actually resolve a key for. A row that'stmp_MissingCostbut nottmp_MissingPricesskips the join — all joined columns come back null — and falls through the existingcase()blocks' null-safe branches straight to theEffectiveCost-based fallback. That's the same outcome #2214 asked for, and it needed zero new branches in thecase()blocks — they already handle the join's no-match case correctly, just weren't reachable for these rows before.MissingListCostis cleared fromx_SourceChangesonceListCostis known-good post-repair, in both files.Scope notes (defect 3 excluded)
v1.2 already has
x_SourceValues/checkRealprovenance tracking, and it automatically picks up newly-repaired rows —old_ListCostis captured before the gate runs, socheckReal('ListCost', old_ListCost, ListCost)downstream correctly detects the change. No new code needed.v1.0's
Costs_final_v1_0schema does not getx_SourceValuesand this is intentional, not deferred: v1.0 is kept specifically so people can revert to legacy behavior, and its schema must never gain new columns. Confirmed via a Pester test that assertsx_SourceValuesnever appears inIngestionSetup_v1_0.kql.Test plan
HubsMissingCostGate.Tests.ps1— asserts both KQL files have the gate split, the join is correctly scoped, the merge-back uses the right gate, theMissingListCostclearing logic is present, and v1.0's schema stays frozenMissingCostGateSplit.kql— executable equivalence harness (same pattern asContractedCostTolerance.kql) covering: no meter/offer ID, only one of the two IDs set, unused spend commitments, non-Microsoft providers, already-correct prices, no effective price. Verified by hand against the gate logic (PASS = 0 rows)Related: #2214, #2235, #2286
🤖 Generated with Claude Code