| name | commitment-analysis |
| description | RI/Savings Plan utilization, coverage of compute spend, new purchase recommendations, and an optional investigation when utilization drops. WHEN: "commitment benefits", "RI utilization", "savings plan utilization", "reservation coverage", "utilization dropped", "how much am I saving", "new RI recommendation". INVOKES: list_benefit_utilization, get_benefit_recommendations, query_costs, list_reservation_transactions, generate_query, validate_query, execute_query |
| license | MIT |
| metadata | {"version":"1.0.0"} |
Commitment Benefits & Utilization
Reports current RI / Savings Plan utilization, how much of eligible spend is covered, and what new commitments are recommended. If utilization is low, runs an optional ARG-based investigation to explain why.
Scope note: list_benefit_utilization requires a billing scope (/providers/Microsoft.Billing/billingAccounts/{id}). If the user only provides a subscription, ask for the billing account ID before running.
The ARG queries in Step 4 take subscription-id placeholders (<sub-id-1>, <sub-id-2>) — replace with the subscriptions where the reservation is scoped (Single/RG) or applied (Shared). To target a management group, first resolve member subs via ResourceContainers | where type == 'microsoft.resources/subscriptions' | where properties.managementGroupAncestorsChain has '<mg-name>' | project subscriptionId.
Run every ARG query through the generate → validate → execute workflow. The queries in Step 4 are ready to run, but pass each one to validate_query first — it confirms the syntax and property paths before you spend a call on execute_query. If you need to adapt a query to a different resource type or field, draft it with generate_query (a natural-language prompt), then validate and execute.
Step 1: Portfolio Utilization
Call list_benefit_utilization for the billing scope. The summary returns a utilization percentage per benefit, each identified by its benefitId / benefitOrderId and kind (Reservation or SavingsPlan). For each commitment, report:
- Utilization % with status: 🟢 ≥90% | 🟡 70–89% | 🔴 <70%
- Which benefit it is —
kind (RI vs Savings Plan) and the benefit/order identifier.
The commitment's term, applied scope, and committed SKU/region describe the underlying reservation or savings-plan order, not the utilization summary — read them from the order itself if the user needs them.
If any commitment is below 90%, hold onto it for the optional investigation in Step 4.
Step 2: Coverage of Eligible Spend
Call query_costs with metric=AmortizedCost, groupBy=PricingModel for a recent window. There's no built-in 30-day timeframe, so use timeframe=Custom with a trailing 30-day from/to (or timeframe=TheLastMonth for the prior calendar month). Amortized cost spreads reservation and savings-plan purchases across their term, so committed usage shows up under the Reservation / SavingsPlan pricing models rather than as a lump-sum purchase.
Report the breakdown by pricing model — how much recent spend ran on Reservation / SavingsPlan versus OnDemand. A large OnDemand share relative to the committed buckets points to headroom for new commitments; size any new purchase from Azure's recommendation in Step 3, not from this split.
Do not compute a "real savings vs PAYG retail" number here. An accurate comparison requires per-SKU retail-price lookup against actual usage hours and is out of scope for this skill — defer to get_benefit_recommendations (Step 3) for vetted savings estimates from Azure.
Step 3: New Purchase Recommendations
Call get_benefit_recommendations for the user's scope. Show, as returned by the API:
- Recommended commitment —
kind (Reservation or Savings Plan) and armSkuName (for a Savings Plan this is Compute_Savings_Plan, which is SKU- and region-flexible), with the hourly commitmentAmount.
- Estimated savings —
savingsAmount and savingsPercentage. These are computed over the look-back period (e.g., the last 60 days), not per month — state the period rather than implying a monthly figure.
- Projected effect if purchased —
coveragePercentage and averageUtilizationPercentage.
Frame these as additions to the current portfolio (Step 1), not replacements.
Step 4 (Optional): Utilization Drop Investigation
Run only if Step 1 shows utilization <90% or the user explicitly asks why utilization dropped.
Start by checking the commitment ledger with list_reservation_transactions (billing scope; pass a from date — the API looks back at most 92 days, so use a window inside the last ~3 months). Each transaction carries an eventType (Purchase, Refund, Exchange, Cancel) and eventDate:
- A recent Refund, Exchange, or Cancel directly removes covered capacity and explains a utilization drop.
- A Purchase date plus the commitment's term tells you whether a benefit has reached end of term (expiry isn't itself a transaction, so reason about it from the purchase date and term).
- The API only covers the last ~3 months; for older events it points to Cost Management Exports — say so rather than implying nothing changed.
Then rule out the other simple causes:
- Scope mismatch? Is the reservation scoped Single/RG when resources moved subscriptions or RGs?
- Region mismatch? Are new resources deploying in a region the reservation doesn't cover?
If those don't explain it, call execute_query for two Azure Resource Graph queries (one inventory, one change history):
-
Inventory of resources matching the committed type. Adapt the resource type to the reservation (e.g., Microsoft.Compute/virtualMachines for VM RIs):
Resources
| where type =~ '<resource-type-from-reservation>'
| where subscriptionId in ('<sub-id-1>', '<sub-id-2>')
| summarize count() by tostring(properties.hardwareProfile.vmSize), location
| order by count_ desc
Drop in the SKU/tier projection that matches the resource type. A lower count than the reservation expects explains a utilization drop.
-
Recent deletions and SKU changes via resourcechanges:
resourcechanges
| where subscriptionId in ('<sub-id-1>', '<sub-id-2>')
| where properties.changeType in ('Delete', 'Update')
| where properties.targetResourceType =~ '<resource-type-from-reservation>'
| project targetResource=tostring(properties.targetResourceId),
changeType=tostring(properties.changeType),
timestamp=todatetime(properties.changeAttributes.timestamp),
changedBy=tostring(properties.changeAttributes.changedBy)
| order by timestamp desc
| limit 30
For SKU changes, inspect the row's properties.propertyChanges array (each entry has propertyName, previousValue, newValue) to confirm the SKU/size shifted.
⚠️ Caveats on resourcechanges: results depend on tenant permissions and on whether change tracking captured the event. An empty result does not prove nothing changed — it may mean the change is outside the retention window or not visible at the current scope.
Presentation
- Utilization summary — Per-commitment % with status indicators.
- Commitment coverage — recent spend split across
Reservation / SavingsPlan versus OnDemand.
- New recommendations — the commitment(s) returned by
get_benefit_recommendations, with API-reported savings over the look-back period.
- (If Step 4 ran) Root-cause summary — What changed and when, e.g., "3 VMs deleted on April 15; SKU resized on 2 VMs on April 18." Include a one-line remediation hint:
- Exchange the reservation for a different SKU/region (within exchange window)
- Redeploy workloads to match the committed capacity
- Consider Savings Plans (SKU/region flexible) for the next purchase
- Refund if the commitment is no longer needed (subject to policy)