You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
FOCUS Prices: ""negotiated"" discount on SavingsPlan rows mirrors the on-demand meter - original SP MarketPrice/UnitPrice are dropped #2198
On SavingsPlan rows, ListUnitPrice, ContractedUnitPrice, and x_BaseUnitPrice are all back-filled from the matching on-demand (Consumption) row, and the row's raw MarketPrice / UnitPrice are discarded (raw UnitPrice survives only as x_EffectiveUnitPrice). As a result the negotiated-discount columns on SP rows are not SP-specific — they just echo the underlying PayGo meter's discount, so you cannot compute a "negotiated discount per SP meter" from Prices_v1_2().
x_ContractedUnitPriceDiscount(Percent) on an SP row = the on-demand meter's negotiated discount, not anything about the SP. Across a tenant it collapses to the flat PayGo EA/MCA discount (e.g. a uniform ~24.5% on compute), which is misleading when read as "SP negotiated discount."
Because the back-fill key omits Term, ListUnitPrice / ContractedUnitPrice are identical for the 1-yr and 3-yr SP rows of the same meter; only x_EffectiveUnitPrice varies by term. So x_ContractedUnitPriceDiscountPercent is term-independent on SP rows.
The original SP MarketPrice is not retained in any output column of Prices_v1_2().
Questions / requests
Is populating x_ContractedUnitPriceDiscount* on SP rows intended, given it only reflects the on-demand twin?
Could the transform preserve the raw SP MarketPrice (e.g. as x_SkuMarketPrice / x_SavingsPlanListPrice) so consumers can distinguish SP-native list pricing from the back-filled on-demand list price?
Suggest documenting that, for SP rows, negotiated-discount columns are inherited from the on-demand meter and only x_EffectiveUnitPriceDiscount* reflects the SP's own benefit.
Hi Dirk Brinkmann (@DirkBrinkmann) — this is an excellent, precise write-up, and your reading of the transform is correct. Thanks for taking the time.
To confirm in plain terms: for SavingsPlan rows, Prices_transform_v1_2() currently drops the row's own MarketPrice/UnitPrice (raw UnitPrice survives only as x_EffectiveUnitPrice) and back-fills ListUnitPrice/ContractedUnitPrice/x_BaseUnitPrice from the matching on-demand (Consumption) meter. So, exactly as you found:
the negotiated-discount columns on SP rows reflect the on-demand twin's discount, not anything SP-specific;
because the back-fill key doesn't include term, the 1-year and 3-year SP rows of the same meter get identical List/Contracted prices (only x_EffectiveUnitPrice varies by term); and
the raw SP MarketPrice isn't retained in any output column.
This relates to known behavior in the price-sheet transform — the cross-row SP→Consumption lookup is tracked in #1625, and an earlier, narrower report of the SP MarketPrice being nulled is in #2115. Your write-up adds useful detail on the SavingsPlan-specific consequences.
In the meantime, if you need the SP-native list price you can read it straight from the raw data — the MarketPrice/UnitPrice on the SavingsPlan rows in Prices_raw carry the SP's own retail/contracted rates before the transform overwrites them.
Root cause: Confirmed in current dev. In Prices_transform_v1_2() (src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql):
:55-56 null out ContractedUnitPrice/ListUnitPrice for x_SkuPriceType == 'SavingsPlan' (iff(x_SkuPriceType != 'SavingsPlan', UnitPrice/MarketPrice, real(null))), discarding the SP row's own UnitPrice/MarketPrice. Raw UnitPrice is retained only as x_EffectiveUnitPrice.
:80-86lookup kind=leftouter against Consumption rows (distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) and coalesce the nulled SP columns from the on-demand twin.
Net: SP ListUnitPrice/ContractedUnitPrice/x_BaseUnitPrice are the on-demand meter's values, so x_ContractedUnitPriceDiscount(Percent) = ListUnitPrice - ContractedUnitPrice on an SP row is the PayGo meter's negotiated discount (collapses to the flat EA/MCA compute discount tenant-wide) and is term-independent because the key omits term. The raw SP MarketPrice appears in no output column. All three of the reporter's claims reproduce exactly.
Scope / impact: All v1_2 deployments (ADX + Fabric), EA and MCA, any tenant with SavingsPlan coverage. Affects anyone deriving an "SP negotiated discount" from Prices_v1_2() and any join of SP rows to costs on price keys. Same root code underlies v1_0 (IngestionSetup_v1_0.kql:64-65,75,90).
Relationship: This is the SavingsPlan-specific face of #1625 (cross-row Prices_transform operations broken by per-batch ADX update-policy execution). PR #2037 (closed unmerged 2026-03-25, mergedAt: null) already designed the fix: remove the SP price-nulling and the Consumption back-fill lookup entirely, keeping the SP row's own MarketPrice→ListUnitPrice and UnitPrice→ContractedUnitPrice, with EA/MCA price-sheet validation cited in the PR confirming those raw fields are the SP retail/contracted rates. That approach directly resolves the reporter's points 1 and 3. The narrower #2115 (MarketPrice nulled for SP) is a subset. #2178 (SP lookup fan-out) was self-retracted by its author as unverified.
Keep the back-fill but additionally preserve raw SP pricing in new x_ columns (e.g. x_SkuMarketPrice) and document that SP discount columns are inherited. Trade-off: additive/backward-compatible, but leaves the misleading discount semantics and doesn't fix Prices_transform_v1_0 function does not work as intended #1625's per-batch breakage.
Document-only — note that on SP rows the negotiated-discount columns are inherited from the on-demand meter and only x_EffectiveUnitPrice(Discount*) reflects the SP's own benefit. Trade-off: zero code change; data stays misleading.
Recommendation: Needs a team decision before committing to a direction — this isn't a clear-cut fix. Option 1 (revive #2037) is the most complete and was already drafted with EA/MCA validation, but PR #2037 was closed unmerged, so changing the SP price-column semantics is evidently a non-trivial call (backward-compat for any consumer currently reading the on-demand-twin values, and the per-batch execution constraint behind #1625). Options 1 (semantic fix), 2 (additive raw-price x_ columns, back-compat), and 3 (document-only) should be weighed together, ideally folded into the #1625 work item rather than decided in triage.
🤖 [AI] Update: revisited reviving PR #2037's approach (remove the SP→Consumption price backfill lookup) for this release, but per repo owner feedback: #2037 traded one incomplete state for another — it fixed some rows while breaking others, without solving the actual root cause (ADX update policies executing per-ingestion-batch, so cross-row operations like the SP↔Consumption lookup only ever see the current file's data, not the full price sheet).
The real fix likely needs a two-phase ingestion approach: ingest raw price rows per-batch as today, then run a second pass after all files for a given export have landed to resolve cross-row logic (SP price backfill, eligibility, etc.) against the complete dataset — conceptually similar to how #2246 sidestepped this same per-batch limitation for commitment discount eligibility by moving to a separately-computed reference table instead of an in-transform lookup.
This is real design work, not a quick fix, so moving to v16 rather than rushing something into this release. Related: #1625 (the general per-batch cross-row problem), #2037 (closed, incomplete fix), #2115 (narrower MarketPrice-nulled report).
Activity
microsoft-github-policy-service commentedon Jun 26, 2026
Thank you for submitting this issue! We try to respond within 3 business days. In the meantime:
RolandKrummenacher commentedon Jun 28, 2026
Hi Dirk Brinkmann (@DirkBrinkmann) — this is an excellent, precise write-up, and your reading of the transform is correct. Thanks for taking the time.
To confirm in plain terms: for
SavingsPlanrows,Prices_transform_v1_2()currently drops the row's ownMarketPrice/UnitPrice(rawUnitPricesurvives only asx_EffectiveUnitPrice) and back-fillsListUnitPrice/ContractedUnitPrice/x_BaseUnitPricefrom the matching on-demand (Consumption) meter. So, exactly as you found:List/Contractedprices (onlyx_EffectiveUnitPricevaries by term); andMarketPriceisn't retained in any output column.This relates to known behavior in the price-sheet transform — the cross-row SP→Consumption lookup is tracked in #1625, and an earlier, narrower report of the SP
MarketPricebeing nulled is in #2115. Your write-up adds useful detail on the SavingsPlan-specific consequences.In the meantime, if you need the SP-native list price you can read it straight from the raw data — the
MarketPrice/UnitPriceon theSavingsPlanrows inPrices_rawcarry the SP's own retail/contracted rates before the transform overwrites them.Thanks again — really useful analysis.
RolandKrummenacher commentedon Jun 28, 2026
🔍 Root-cause analysis (engineering)
Root cause: Confirmed in current
dev. InPrices_transform_v1_2()(src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql)::55-56null outContractedUnitPrice/ListUnitPriceforx_SkuPriceType == 'SavingsPlan'(iff(x_SkuPriceType != 'SavingsPlan', UnitPrice/MarketPrice, real(null))), discarding the SP row's ownUnitPrice/MarketPrice. RawUnitPriceis retained only asx_EffectiveUnitPrice.:66buildstmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)— nox_SkuTerm.:80-86lookup kind=leftouteragainstConsumptionrows (distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) andcoalescethe nulled SP columns from the on-demand twin.Net: SP
ListUnitPrice/ContractedUnitPrice/x_BaseUnitPriceare the on-demand meter's values, sox_ContractedUnitPriceDiscount(Percent) = ListUnitPrice - ContractedUnitPriceon an SP row is the PayGo meter's negotiated discount (collapses to the flat EA/MCA compute discount tenant-wide) and is term-independent because the key omits term. The raw SPMarketPriceappears in no output column. All three of the reporter's claims reproduce exactly.Scope / impact: All v1_2 deployments (ADX + Fabric), EA and MCA, any tenant with SavingsPlan coverage. Affects anyone deriving an "SP negotiated discount" from
Prices_v1_2()and any join of SP rows to costs on price keys. Same root code underlies v1_0 (IngestionSetup_v1_0.kql:64-65,75,90).Relationship: This is the SavingsPlan-specific face of #1625 (cross-row
Prices_transformoperations broken by per-batch ADX update-policy execution). PR #2037 (closed unmerged 2026-03-25,mergedAt: null) already designed the fix: remove the SP price-nulling and the Consumption back-fill lookup entirely, keeping the SP row's ownMarketPrice→ListUnitPriceandUnitPrice→ContractedUnitPrice, with EA/MCA price-sheet validation cited in the PR confirming those raw fields are the SP retail/contracted rates. That approach directly resolves the reporter's points 1 and 3. The narrower #2115 (MarketPrice nulled for SP) is a subset. #2178 (SP lookup fan-out) was self-retracted by its author as unverified.Fix options:
:55-56) and the Consumption lookup (:80-86); SP rows keep their own list/contracted prices. Fixes all three points + the per-batch correctness problem in Prices_transform_v1_0 function does not work as intended #1625. Trade-off: SP price-column semantics change (consumers relying on the on-demand-twin values would see different numbers); re-confirm the EA/MCA validation before merge.x_columns (e.g.x_SkuMarketPrice) and document that SP discount columns are inherited. Trade-off: additive/backward-compatible, but leaves the misleading discount semantics and doesn't fix Prices_transform_v1_0 function does not work as intended #1625's per-batch breakage.x_EffectiveUnitPrice(Discount*)reflects the SP's own benefit. Trade-off: zero code change; data stays misleading.Recommendation: Needs a team decision before committing to a direction — this isn't a clear-cut fix. Option 1 (revive #2037) is the most complete and was already drafted with EA/MCA validation, but PR #2037 was closed unmerged, so changing the SP price-column semantics is evidently a non-trivial call (backward-compat for any consumer currently reading the on-demand-twin values, and the per-batch execution constraint behind #1625). Options 1 (semantic fix), 2 (additive raw-price
x_columns, back-compat), and 3 (document-only) should be weighed together, ideally folded into the #1625 work item rather than decided in triage.flanakin commentedon Aug 26, 2026
🤖 [AI] Update: revisited reviving PR #2037's approach (remove the SP→Consumption price backfill lookup) for this release, but per repo owner feedback: #2037 traded one incomplete state for another — it fixed some rows while breaking others, without solving the actual root cause (ADX update policies executing per-ingestion-batch, so cross-row operations like the SP↔Consumption lookup only ever see the current file's data, not the full price sheet).
The real fix likely needs a two-phase ingestion approach: ingest raw price rows per-batch as today, then run a second pass after all files for a given export have landed to resolve cross-row logic (SP price backfill, eligibility, etc.) against the complete dataset — conceptually similar to how #2246 sidestepped this same per-batch limitation for commitment discount eligibility by moving to a separately-computed reference table instead of an in-transform lookup.
This is real design work, not a quick fix, so moving to v16 rather than rushing something into this release. Related: #1625 (the general per-batch cross-row problem), #2037 (closed, incomplete fix), #2115 (narrower MarketPrice-nulled report).