Skip to content

fix(hubs): repair ListCost/ContractedCost for rows without a meter/offer ID - #2290

Draft
Michael Flanakin (flanakin) wants to merge 3 commits into
devfrom
flanakin/2286-ingestion-cost-repair
Draft

Michael Flanakin (flanakin) wants to merge 3 commits into
devfrom
flanakin/2286-ingestion-cost-repair

Conversation

@flanakin

@flanakin Michael Flanakin (flanakin) commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

🛠️ Description

Fixes defects 2 and 4 from #2214 (defect 1 is #2248; defect 3 is out of scope — see below):

Splits the combined gate into two: tmp_MissingCost (no meter/offer ID requirement) now gates entry into the repair fallback; tmp_MissingPrices narrows that further to rows the price-sheet join can actually resolve a key for. A row that's tmp_MissingCost but not tmp_MissingPrices skips the join — all joined columns come back null — and falls through the existing case() blocks' null-safe branches straight to the EffectiveCost-based fallback. That's the same outcome #2214 asked for, and it needed zero new branches in the case() blocks — they already handle the join's no-match case correctly, just weren't reachable for these rows before. MissingListCost is cleared from x_SourceChanges once ListCost is known-good post-repair, in both files.

Scope notes (defect 3 excluded): v1.2 already has x_SourceValues/checkReal provenance tracking, and it automatically picks up newly-repaired rows — old_ListCost is captured before the gate runs, so checkReal('ListCost', old_ListCost, ListCost) downstream correctly detects the change. No new code needed. v1.0's Costs_final_v1_0 schema does not get x_SourceValues and 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 asserts x_SourceValues never appears in IngestionSetup_v1_0.kql.

Fixes #2214, #2235
Related: #2286

📷 Screenshots

Not applicable — KQL ingestion transform change, no UI.

📋 Checklist

🔬 How did you test this change?

  • 🤏 Lint tests
  • 🤞 PS -WhatIf / az validate
  • 👍 Manually deployed + verified
  • 💪 Unit tests
  • 🙌 Integration tests

Added HubsMissingCostGate.Tests.ps1 asserting both KQL files have the gate split, the join is correctly scoped, the merge-back uses the right gate, the MissingListCost clearing logic is present, and v1.0's schema stays frozen. Added MissingCostGateSplit.kql — executable equivalence harness (same pattern as ContractedCostTolerance.kql) covering rows missing one/both IDs, unused spend commitments, non-Microsoft providers, and already-correct prices; verified by hand against the gate logic (PASS = 0 rows on a real Kusto database, not yet run against one). Full PowerShell lint suite passes (3586/3586); full unit test suite passes (2295/2295). Not yet manually deployed or run against a live ingestion pipeline with real cost data — see deploy-to-test below.

📦 Deploy to test?

  • Hubs + ADX (managed)
  • Hubs + Fabric (manual) — URI:
  • Hubs (manual)
  • Hubs (no data)
  • Workbooks
  • Alerts

🙋‍♀️ Do any of the following that apply?

  • 🚨 This is a breaking change.
  • 🤏 The change is less than 20 lines of code.

📑 Did you update docs/changelog.md?

  • ✅ Updated changelog (required for dev PRs)
  • ➡️ Will add log in a future PR (feature branch PRs only)
  • ❎ Log not needed (small/internal change)

📖 Did you update documentation?

  • ✅ Public docs in docs (required for dev)
  • ✅ Public docs in docs-mslearn (required for dev)
  • ✅ Internal dev docs in docs-wiki (required for dev)
  • ✅ Internal dev docs in src (required for dev)
  • ➡️ Will add docs in a future PR (feature branch PRs only)
  • ❎ Docs not needed (small/internal change)

Internal ingestion-pipeline fix with no user-facing behavior surface beyond the changelog entry — no parameter, template, or public API changed.

Draft — this touches the ingestion pipeline's write path; wants a second pass on the case() fallback behavior for previously-unreachable rows before merging, and a live-data verification run given deploy to test is checked but hasn't actually run yet.

🤖 Generated with Claude Code

…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>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployed to pr-2290-adx.

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

3 participants