Skip to content

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

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

fix(hubs): repair ListCost/ContractedCost for rows without a meter/offer ID#2290
Michael Flanakin (flanakin) wants to merge 1 commit into
devfrom
flanakin/2286-ingestion-cost-repair

Conversation

@flanakin

Copy link
Copy Markdown
Collaborator

Summary

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

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_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.

Test plan

  • Added HubsMissingCostGate.Tests.ps1 — asserts 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: 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)
  • Full PowerShell lint suite passes (3586/3586)
  • Full unit test suite passes (2295/2295)
  • Maintainer review — draft because this touches the ingestion pipeline's write path; want a second pass on the case() fallback behavior for previously-unreachable rows before merging

Related: #2214, #2235, #2286

🤖 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>
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.

2 participants