Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs-mslearn/toolkit/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: FinOps toolkit changelog
description: Review the latest features and enhancements in the FinOps toolkit, including updates to FinOps hubs, Power BI reports, and more.
author: MSBrett
ms.author: brettwil
ms.date: 04/29/2026
ms.date: 05/06/2026
ms.topic: reference
ms.service: finops
ms.subservice: finops-toolkit
Expand Down Expand Up @@ -31,6 +31,18 @@ The following section lists features and enhancements that are currently in deve
- Added Claude Code plugin with skills for FinOps hubs and Azure Cost Management ([#2043](https://github.com/microsoft/finops-toolkit/pull/2043)).
- Added 4 agents (CFO, FinOps practitioner, database query, hubs agent), 5 commands (`/ftk-hubs-connect`, `/ftk-hubs-healthCheck`, `/ftk-mom-report`, `/ftk-ytd-report`, `/ftk-cost-optimization`), and an output style.
- Linked to the existing KQL query catalog in `src/queries/` from the plugin.
- **Changed**
- Updated plugin skill files to reflect FOCUS 1.3 / 1.4-preview hub schemas, the new `ContractCommitment()` function, and the deprecation of `ProviderName` / `PublisherName` ([#2120](https://github.com/microsoft/finops-toolkit/issues/2120)).

### FinOps hubs v15.0.0

- **Added**
- Added FOCUS 1.3 hub schema (`v1_3`). New cost and usage columns: `AllocatedMethodId`, `AllocatedMethodDetails`, `AllocatedResourceId`, `AllocatedResourceName`, `AllocatedTags`, `ContractApplied`, `ServiceProviderName`, `HostProviderName`. New supplemental dataset: `ContractCommitment` ([#2120](https://github.com/microsoft/finops-toolkit/issues/2120)).
- Added FOCUS 1.4-preview hub schema (`v1_4`). Drops `ProviderName` and `PublisherName` from cost and usage. Expands `ContractCommitment` by 14 columns (`BenefitCategory`, `ContractCommitmentApplicability`, `Created`, `DiscountPercentage`, `DurationType`, `FulfillmentInterval`, `LastUpdated`, `LifecycleStatus`, `Model`, `OfferCategory`, `PaymentInterval`, `PaymentModel`, `PaymentUpfrontPercentage`, `PricingCurrencyContractCommitmentCost`). Marked **preview** because FOCUS 1.4 is still in working draft and may change before ratification.
- Added unversioned `ContractCommitment()` function aliasing to `ContractCommitment_v1_3()`.
- **Changed**
- Retargeted unversioned `Costs()`, `Prices()`, `CommitmentDiscountUsage()`, `Recommendations()`, and `Transactions()` aliases to their `_v1_3` counterparts. Unversioned aliases stay pinned to the latest GA schema (v1_3) — `_v1_4` is opt-in only while in preview.
- Refreshed the canonical "add a new FOCUS version" procedure in [src/templates/finops-hub/docs/README.md](https://github.com/microsoft/finops-toolkit/tree/dev/src/templates/finops-hub/docs/README.md) with multi-version-cycle, plugin, and changelog steps.

### Bicep Registry module pending updates

Expand Down
228 changes: 228 additions & 0 deletions src/powershell/Tests/Unit/HubsFocusSchemas.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'HubsFocusSchemas' {

BeforeAll {
$repoRoot = (Resolve-Path "$PSScriptRoot/../../../..").Path
$scriptsPath = Join-Path $repoRoot 'src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts'
$rawTablesContent = Get-Content -Path (Join-Path $scriptsPath 'IngestionSetup_RawTables.kql') -Raw

$ingestionFiles = @{
v1_0 = Get-Content -Path (Join-Path $scriptsPath 'IngestionSetup_v1_0.kql') -Raw
v1_2 = Get-Content -Path (Join-Path $scriptsPath 'IngestionSetup_v1_2.kql') -Raw
v1_3 = Get-Content -Path (Join-Path $scriptsPath 'IngestionSetup_v1_3.kql') -Raw
v1_4 = Get-Content -Path (Join-Path $scriptsPath 'IngestionSetup_v1_4.kql') -Raw
}

$hubFiles = @{
v1_0 = Get-Content -Path (Join-Path $scriptsPath 'HubSetup_v1_0.kql') -Raw
v1_2 = Get-Content -Path (Join-Path $scriptsPath 'HubSetup_v1_2.kql') -Raw
v1_3 = Get-Content -Path (Join-Path $scriptsPath 'HubSetup_v1_3.kql') -Raw
v1_4 = Get-Content -Path (Join-Path $scriptsPath 'HubSetup_v1_4.kql') -Raw
Latest = Get-Content -Path (Join-Path $scriptsPath 'HubSetup_Latest.kql') -Raw
}

$appBicep = Get-Content -Path (Join-Path $repoRoot 'src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/app.bicep') -Raw
$buildConfig = Get-Content -Path (Join-Path $repoRoot 'src/templates/finops-hub/.build.config') -Raw
}

Context 'FOCUS 1.3 columns in Costs_raw' {

BeforeAll {
# Extract just the Costs_raw alter block (not Costs_final or any other table).
$script:costsRawBlock = if ($rawTablesContent -match '(?ms)\.alter table Costs_raw \(\r?\n(.*?)\r?\n\)') { $Matches[1] } else { '' }
}

It 'Costs_raw block was extracted' {
$costsRawBlock | Should -Not -BeNullOrEmpty
}

It 'Adds <_> to Costs_raw' -ForEach @(
'AllocatedMethodId', 'AllocatedMethodDetails', 'AllocatedResourceId',
'AllocatedResourceName', 'AllocatedTags', 'ContractApplied',
'ServiceProviderName', 'HostProviderName'
) {
$costsRawBlock | Should -Match "(?m)^\s+$_\s*:"
}
}

Context 'ContractCommitment_raw exists with FOCUS 1.3 + 1.4 columns' {

BeforeAll {
# The Redefine-all-columns alter-table block is the second occurrence; match all and pick it.
$allBlocks = [regex]::Matches($rawTablesContent, '(?ms)\.alter table ContractCommitment_raw \(\r?\n(.*?)\r?\n\)')
$script:contractCommitmentRawBlock = if ($allBlocks.Count -ge 1) { $allBlocks[$allBlocks.Count - 1].Groups[1].Value } else { '' }
}

It 'Defines ContractCommitment_raw' {
$rawTablesContent | Should -Match '\.alter table ContractCommitment_raw \('
}

It 'ContractCommitment_raw column block was extracted' {
$contractCommitmentRawBlock | Should -Not -BeNullOrEmpty
}

It 'Includes FOCUS 1.3 column <_>' -ForEach @(
'BillingCurrency', 'ContractCommitmentCategory', 'ContractCommitmentCost',
'ContractCommitmentId', 'ContractCommitmentPeriodEnd', 'ContractCommitmentPeriodStart',
'ContractCommitmentQuantity', 'ContractCommitmentType', 'ContractCommitmentUnit',
'ContractId', 'ContractPeriodEnd', 'ContractPeriodStart', 'InvoiceIssuerName',
'PricingCurrency'
) {
$contractCommitmentRawBlock | Should -Match "(?m)^\s+$_\s*:"
}

It 'Includes FOCUS 1.4 column <_>' -ForEach @(
'BenefitCategory', 'ContractCommitmentApplicability', 'Created',
'DiscountPercentage', 'DurationType', 'FulfillmentInterval', 'LastUpdated',
'LifecycleStatus', 'Model', 'OfferCategory', 'PaymentInterval', 'PaymentModel',
'PaymentUpfrontPercentage', 'PricingCurrencyContractCommitmentCost'
) {
$contractCommitmentRawBlock | Should -Match "(?m)^\s+$_\s*:"
}
}

Context 'IngestionSetup_v1_3.kql' {

BeforeAll {
$script:costsFinalV13Block = if ($ingestionFiles.v1_3 -match '(?ms)\.create-merge table Costs_final_v1_3 \(\r?\n(.*?)\r?\n\)') { $Matches[1] } else { '' }
}

It 'Defines Costs_transform_v1_3' {
$ingestionFiles.v1_3 | Should -Match 'Costs_transform_v1_3\(\)'
}

It 'Defines Costs_final_v1_3 table' {
$ingestionFiles.v1_3 | Should -Match '\.create-merge table Costs_final_v1_3'
}

It 'Defines ContractCommitment_transform_v1_3' {
$ingestionFiles.v1_3 | Should -Match 'ContractCommitment_transform_v1_3\(\)'
}

It 'Defines ContractCommitment_final_v1_3 table' {
$ingestionFiles.v1_3 | Should -Match '\.create-merge table ContractCommitment_final_v1_3'
}

It 'Costs_final_v1_3 block was extracted' {
$costsFinalV13Block | Should -Not -BeNullOrEmpty
}

It 'Costs_final_v1_3 includes FOCUS 1.3 column <_>' -ForEach @(
'AllocatedMethodId', 'AllocatedMethodDetails', 'AllocatedResourceId',
'AllocatedResourceName', 'AllocatedTags', 'ContractApplied',
'ServiceProviderName', 'HostProviderName'
) {
$costsFinalV13Block | Should -Match "(?m)^\s+$_\s*:"
}

It 'Costs_final_v1_3 still includes deprecated <_> for back compat' -ForEach @(
'ProviderName', 'PublisherName'
) {
$costsFinalV13Block | Should -Match "(?m)^\s+$_\s*:"
}
}

Context 'IngestionSetup_v1_4.kql' {

BeforeAll {
$script:costsFinalV14Block = if ($ingestionFiles.v1_4 -match '(?ms)\.create-merge table Costs_final_v1_4 \(\r?\n(.*?)\r?\n\)') { $Matches[1] } else { '' }
$script:contractCommitmentFinalV14Block = if ($ingestionFiles.v1_4 -match '(?ms)\.create-merge table ContractCommitment_final_v1_4 \(\r?\n(.*?)\r?\n\)') { $Matches[1] } else { '' }
}

It 'Defines Costs_transform_v1_4' {
$ingestionFiles.v1_4 | Should -Match 'Costs_transform_v1_4\(\)'
}

It 'Defines Costs_final_v1_4 table' {
$ingestionFiles.v1_4 | Should -Match '\.create-merge table Costs_final_v1_4'
}

It 'Costs_final_v1_4 block was extracted' {
$costsFinalV14Block | Should -Not -BeNullOrEmpty
}

It 'Costs_final_v1_4 does NOT include deprecated <_> (removed in 1.4)' -ForEach @(
'ProviderName', 'PublisherName'
) {
$costsFinalV14Block | Should -Not -Match "(?m)^\s+$_\s*:"
}

It 'ContractCommitment_final_v1_4 includes FOCUS 1.4 column <_>' -ForEach @(
'BenefitCategory', 'ContractCommitmentApplicability', 'Created',
'DiscountPercentage', 'DurationType', 'FulfillmentInterval', 'LastUpdated',
'LifecycleStatus', 'Model', 'OfferCategory', 'PaymentInterval', 'PaymentModel',
'PaymentUpfrontPercentage', 'PricingCurrencyContractCommitmentCost'
) {
$contractCommitmentFinalV14Block | Should -Match "(?m)^\s+$_\s*:"
}
}

Context 'HubSetup_v1_3.kql' {

It 'Defines <_>' -ForEach @(
'CommitmentDiscountUsage_v1_3', 'ContractCommitment_v1_3',
'Costs_v1_3', 'Prices_v1_3', 'Recommendations_v1_3', 'Transactions_v1_3'
) {
$hubFiles.v1_3 | Should -Match "$_\(\)"
}

It 'Costs_v1_3 unions all prior versions' -ForEach @(
'Costs_final_v1_0', 'Costs_final_v1_2', 'Costs_final_v1_3'
) {
$hubFiles.v1_3 | Should -Match "database\('Ingestion'\)\.$_"
}
}

Context 'HubSetup_v1_4.kql (preview)' {

It 'Defines <_>' -ForEach @(
'CommitmentDiscountUsage_v1_4', 'ContractCommitment_v1_4',
'Costs_v1_4', 'Prices_v1_4', 'Recommendations_v1_4', 'Transactions_v1_4'
) {
$hubFiles.v1_4 | Should -Match "$_\(\)"
}

It 'Costs_v1_4 unions all prior versions' -ForEach @(
'Costs_final_v1_0', 'Costs_final_v1_2', 'Costs_final_v1_3', 'Costs_final_v1_4'
) {
$hubFiles.v1_4 | Should -Match "database\('Ingestion'\)\.$_"
}

It 'Marks itself as preview in docstring' {
$hubFiles.v1_4 | Should -Match 'FOCUS 1\.4-preview'
}
}

Context 'HubSetup_Latest.kql aliases' {

It 'Aliases <_> to v1_3 (latest GA, not v1_4 preview)' -ForEach @(
'CommitmentDiscountUsage', 'Costs', 'Prices', 'Recommendations', 'Transactions',
'ContractCommitment'
) {
$hubFiles.Latest | Should -Match "(?ms)$_\(\)\s*\{\s*${_}_v1_3\(\)\s*\}"
}

It 'Does NOT alias to v1_4 (preview must be opt-in)' {
$hubFiles.Latest | Should -Not -Match '_v1_4\(\)'
}
}

Context 'Bicep wiring' {

It 'app.bicep loads <_>' -ForEach @(
'IngestionSetup_v1_3.kql', 'IngestionSetup_v1_4.kql',
'HubSetup_v1_3.kql', 'HubSetup_v1_4.kql'
) {
$appBicep | Should -Match ([regex]::Escape($_))
}

It '.build.config lists <_>' -ForEach @(
'IngestionSetup_v1_3.kql', 'IngestionSetup_v1_4.kql',
'HubSetup_v1_3.kql', 'HubSetup_v1_4.kql'
) {
$buildConfig | Should -Match ([regex]::Escape($_))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Create exports for each scope you want to monitor:
| Resource Group | `subscriptions/{subscription-id}/resourceGroups/{rg-name}` |

**Supported Datasets:**
- Cost and usage details (FOCUS) - `1.0`, `1.0r2`

- Cost and usage details (FOCUS) - `1.0`, `1.0r2`, `1.2`, `1.2-preview` (Cost Management export versions). Hubs can also ingest `1.3` and `1.4-preview` if Cost Management ships those exports.
- Price sheet - `2023-05-01` (required for missing prices)
- Reservation details - `2023-03-01`
- Reservation recommendations - `2023-05-01` (required for Rate optimization report)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ All KQL queries are located in `references/queries/`:

**Database Rules:**
- Always use "Hub" database, NEVER "Ingestion"
- Function-based access: `Costs()`, `Prices()`, `Recommendations()`, `Transactions()`
- Function-based access: `Costs()`, `Prices()`, `CommitmentDiscountUsage()`, `ContractCommitment()`, `Recommendations()`, `Transactions()`
- The unversioned functions (`Costs()`, etc.) return the latest GA FOCUS schema. Use versioned functions (`Costs_v1_0()`, `Costs_v1_2()`, `Costs_v1_3()`) to pin to a specific schema. `Costs_v1_4()` is **preview** while FOCUS 1.4 is in working draft and may change.
- FOCUS 1.3 added: `AllocatedMethodId`, `AllocatedMethodDetails`, `AllocatedResourceId`, `AllocatedResourceName`, `AllocatedTags` (data-generator split cost allocation), `ContractApplied` (per-row contract commitment application), `ServiceProviderName` and `HostProviderName` (replacing deprecated `ProviderName` / `PublisherName`). FOCUS 1.4 removes `ProviderName` and `PublisherName` entirely.
- The `ContractCommitment()` function (FOCUS 1.3+) returns provider-confirmed contract commitment metadata — the dataset feeding `ContractApplied` JSON arrays on each cost row.

---

Expand Down
13 changes: 12 additions & 1 deletion src/templates/claude-plugin/agents/ftk-database-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ You are a FinOps Toolkit database specialist with deep expertise in the FinOps h

## Database Architecture

The FinOps hubs database exposes four main analytic functions:
The FinOps hubs database exposes six main analytic functions. The unversioned forms below return the latest GA FOCUS schema; pin to a specific schema with the versioned variants (`Costs_v1_0()`, `Costs_v1_2()`, `Costs_v1_3()`). `Costs_v1_4()` is **preview** while FOCUS 1.4 is in working draft and may change.

### Costs()

Expand All @@ -28,6 +28,9 @@ The primary table for cost and usage analytics. Aligned with the FOCUS specifica
| ChargeCategory | string | Category of the charge (Usage, Purchase) |
| PricingCategory | string | Category of pricing (Standard, Spot, Committed) |
| CommitmentDiscountStatus | string | Status of commitment discount (Used, Unused) |
| ContractApplied | dynamic | (FOCUS 1.3+) JSON array of contract commitments applied to this row |
| ServiceProviderName | string | (FOCUS 1.3+) Provider that made the resource available; replaces deprecated `ProviderName` (removed in 1.4) |
| HostProviderName | string | (FOCUS 1.3+) Underlying infrastructure provider; replaces deprecated `PublisherName` (removed in 1.4) |
| ResourceId | string | Unique identifier for the resource |
| ResourceName | string | Name of the resource |
| ResourceType | string | Type of resource |
Expand All @@ -41,6 +44,14 @@ The primary table for cost and usage analytics. Aligned with the FOCUS specifica

Price sheets with list, contracted, and effective pricing. Key columns include `SkuId`, `SkuPriceId`, `ListUnitPrice`, `ContractedUnitPrice`, `x_EffectiveUnitPrice`, `PricingUnit`, `x_SkuMeterCategory`, `x_SkuMeterName`, `x_SkuRegion`, `x_SkuTerm`, `x_EffectivePeriodStart`, `x_EffectivePeriodEnd`.

### CommitmentDiscountUsage()

Reservation and savings plan utilization, joining commitment discounts to the resources that consumed them. Key columns include `ChargePeriodStart`, `ChargePeriodEnd`, `CommitmentDiscountId`, `CommitmentDiscountQuantity`, `CommitmentDiscountUnit`, `ConsumedQuantity`, `ResourceId`, `ServiceName`, `x_CommitmentDiscountCommittedCount`, `x_CommitmentDiscountNormalizedRatio`.

### ContractCommitment()

(FOCUS 1.3+) Provider-confirmed contract commitment metadata — the dataset that feeds `ContractApplied` JSON arrays on each row in `Costs()`. Key columns include `ContractCommitmentId`, `ContractCommitmentCategory` (Spend / Usage), `ContractCommitmentCost`, `ContractCommitmentQuantity`, `ContractCommitmentPeriodStart`, `ContractCommitmentPeriodEnd`, `ContractId`, `BillingCurrency`, `InvoiceIssuerName`. FOCUS 1.4 preview adds payment-term and lifecycle columns (`PaymentModel`, `PaymentInterval`, `LifecycleStatus`, etc.).

### Recommendations()

Reservation and savings plan recommendations from Microsoft. Key columns include `x_EffectiveCostBefore`, `x_EffectiveCostAfter`, `x_EffectiveCostSavings`, `x_RecommendationDate`, `x_RecommendationDetails` (dynamic), `SubAccountId`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Every recommendation must include:
## FinOps domain conventions

- Reference FinOps Framework capabilities by their official names (e.g., "Managing commitment-based discounts", not "reservation management")
- Use FOCUS specification terminology when discussing cost data fields (e.g., BilledCost, EffectiveCost, ListCost, ContractedCost)
- Use FOCUS specification terminology when discussing cost data fields (e.g., BilledCost, EffectiveCost, ListCost, ContractedCost). Prefer FOCUS 1.3+ names where applicable: `ServiceProviderName` (replaces deprecated `ProviderName`, removed in 1.4) and `HostProviderName` (replaces deprecated `PublisherName`, removed in 1.4); `ContractApplied` and the `ContractCommitment()` dataset for contract commitment tracking.
- Reference maturity levels as Crawl/Walk/Run when discussing FinOps practice maturity
- Cite the six FinOps principles when they are relevant to a recommendation
- For Azure-specific guidance, reference the official Microsoft documentation URL
Expand Down
6 changes: 5 additions & 1 deletion src/templates/finops-hub/.build.config
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_HubInfra.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_RawTables.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_0.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql"
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_3.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_4.kql"
]
},
{
Expand All @@ -43,6 +45,8 @@
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_OpenData.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_0.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_2.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_3.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_4.kql",
"modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_Latest.kql"
]
}
Expand Down
Loading
Loading