Skip to content

Add x_RecommendationCategory and x_RecommendationSubcategory enrichments to v1_4 Recommendations#2194

Draft
flanakin wants to merge 24 commits into
flanakin/focus14from
flanakin/focus14-phase9-rec-category
Draft

Add x_RecommendationCategory and x_RecommendationSubcategory enrichments to v1_4 Recommendations#2194
flanakin wants to merge 24 commits into
flanakin/focus14from
flanakin/focus14-phase9-rec-category

Conversation

@flanakin

Copy link
Copy Markdown
Collaborator

Summary

  • Adds two hub-toolkit enrichment columns to the Recommendations dataset in the FOCUS 1.4 hub schema (v1_4): x_RecommendationCategory and x_RecommendationSubcategory
  • Source values (e.g., Azure Advisor Category) are normalized into a fixed hub taxonomy on ingestion; unmapped categories become an empty string and unmapped subcategories become Other
  • Maps Azure Advisor HighAvailability -> Reliability (the hub taxonomy uses Reliability as the top-level category, with High Availability as one of its subcategories)
  • Documents the allowed taxonomy values in docs-mslearn/toolkit/hubs/data-model.md

Taxonomy decisions

The taxonomy provided in the design notes had a few open markers; the picks below are reflected in code, docs, and tests:

  • "Underused Resources / Low Utilization" -> picked Low Utilization. Clearer FinOps terminology and keeps a clean conceptual split from idle/orphaned resources.
  • "Unused Resources / Idle Resources (includes orphaned)" -> picked Idle Resources. More specific than "Unused", and the parenthetical already noted it covers orphaned resources.
  • "Unused Capacity" -> folded into Idle Resources rather than kept standalone. Unused capacity is conceptually idle compute/storage; a separate bucket would have produced overlap and forced ambiguous mappings at ingestion time. Documented this in the data-model note ("Idle Resources (includes unused capacity and orphaned resources)").
  • Fixed the High Availaibility -> High Availability typo in the Reliability subcategory list.

Files changed

  • src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_4.kql — adds normalization case() for both columns in Recommendations_transform_v1_4() and adds x_RecommendationSubcategory to Recommendations_final_v1_4
  • src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_4.kql — adds x_RecommendationSubcategory to the Recommendations_v1_4() project list (the function uses an explicit projection, so the column must be listed for it to flow through Recommendations())
  • docs-mslearn/toolkit/hubs/data-model.md — documents both columns and the full taxonomy
  • src/powershell/Tests/Unit/HubsFocusSchemas.Tests.ps1 — Pester assertions for column presence, transform projection, and taxonomy coverage
  • docs-mslearn/toolkit/changelog.md — entry under FinOps hubs v15.0.0

Test plan

  • pwsh -Command "Invoke-Pester -Output Detailed -Path ./src/powershell/Tests/Unit/HubsFocusSchemas.Tests.ps1" — all 167 tests pass
  • KQL functions compile in Data Explorer / Fabric after merge
  • Recommendations() returns rows with x_RecommendationCategory populated for reservation recommendations (= Cost) and x_RecommendationSubcategory = Commitment Discount Coverage
  • Advisor HighAvailability recommendations surface as x_RecommendationCategory = Reliability

Stacked on top of flanakin/focus14-phase7-tests-changelog; targets flanakin/focus14.

Generated with Claude Code

flanakin and others added 24 commits May 6, 2026 08:07
Adds IngestionSetup_v1_3.kql with Costs/Prices/CommitmentDiscountUsage/
Recommendations/Transactions transforms and final tables renamed for
FOCUS 1.3. Cost and Usage gains 8 new FOCUS 1.3 columns:
AllocatedMethodId/Details/ResourceId/ResourceName/Tags (data-generator
split cost allocation), ContractApplied (per-row contract commitment
application), ServiceProviderName + HostProviderName (replacing the
deprecated ProviderName/PublisherName, with empty-fallback for back
compat).

Costs_raw now carries the new columns so downstream v1_2 transforms
keep working and v1_3 transforms can read them directly. The v1_3
file is wired into the Bicep deployment alongside v1_0 and v1_2.

The unversioned Costs() function still aliases to v1_2 — phase 2
adds Costs_v1_3() and retargets Latest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds HubSetup_v1_3.kql with Costs_v1_3, Prices_v1_3,
CommitmentDiscountUsage_v1_3, Recommendations_v1_3, and
Transactions_v1_3 that union the new Costs_final_v1_3 with the
existing Costs_final_v1_2 and Costs_final_v1_0 tables.

For v1_2 data unioned into the v1_3 view, the 8 new FOCUS 1.3 columns
default to null/empty and ServiceProviderName/HostProviderName are
populated from the deprecated ProviderName/PublisherName for back
compat. The same defaults apply to the v1_0 union arm on top of the
existing v1_0 -> v1_2 conversion.

HubSetup_Latest.kql aliases now point to *_v1_3 functions so the
unversioned Costs(), Prices(), etc. return the latest schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the FOCUS 1.3 Contract Commitment supplemental dataset:
- ContractCommitment_raw table in IngestionSetup_RawTables.kql with
  the 14 mandatory FOCUS 1.3 columns plus standard hubs source
  metadata.
- ContractCommitment_transform_v1_3 + ContractCommitment_final_v1_3
  in IngestionSetup_v1_3.kql, with update policy mapping raw to final.
- ContractCommitment_v1_3 hub function in HubSetup_v1_3.kql that
  reads the final table directly (no older versions to union).
- Unversioned ContractCommitment alias in HubSetup_Latest.kql.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds IngestionSetup_v1_4.kql as a preview schema for FOCUS 1.4
working_draft. Cost and Usage drops the deprecated ProviderName
and PublisherName columns (removed in 1.4 per spec). Contract
Commitment expands by 14 new columns: BenefitCategory,
ContractCommitmentApplicability (JSON), Created, DiscountPercentage,
DurationType, FulfillmentInterval, LastUpdated, LifecycleStatus,
Model, OfferCategory, PaymentInterval, PaymentModel,
PaymentUpfrontPercentage, PricingCurrencyContractCommitmentCost.

The internal ProviderName/PublisherName fix-up logic in the Costs
transform stays so HostProviderName / ServiceProviderName can fall
back to those values when source data does not yet supply them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds HubSetup_v1_4.kql with the v1_4-preview-shaped hub functions.
Costs_v1_4 unions Costs_final_v1_4, Costs_final_v1_3,
Costs_final_v1_2, and Costs_final_v1_0; ProviderName and
PublisherName are dropped from the v1_4 project list (removed in
1.4 per spec).

ContractCommitment_v1_4 unions ContractCommitment_final_v1_4 with
ContractCommitment_final_v1_3 and extends v1_3 data with empty/null
defaults for the 14 new FOCUS 1.4 columns.

HubSetup_Latest.kql is intentionally NOT retargeted to v1_4. Per
the preview convention in data-model.md, unversioned aliases stay
pinned to the latest GA schema (v1_3) until v1_4 promotes from
preview to GA.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates plugin skill files so plugin guidance reflects the new
hub schemas and FOCUS columns added in this milestone:

- finops-hubs.md: lists the full set of unversioned functions
  (CommitmentDiscountUsage, ContractCommitment included), notes
  the v1_0 / v1_2 / v1_3 / v1_4-preview versioned variants, and
  enumerates the FOCUS 1.3 column additions plus the 1.4
  ProviderName / PublisherName removal.
- finops-hubs-deployment.md: extends the supported FOCUS export
  versions to 1.0r2 / 1.2 / 1.2-preview (current Cost Management)
  and notes that hubs can also ingest 1.3 / 1.4-preview when
  Cost Management ships those exports.
- ftk-database-query.md: expands from four to six analytic
  functions, adds CommitmentDiscountUsage and ContractCommitment
  sections, and surfaces the FOCUS 1.3 columns on the Costs()
  reference table.
- ftk-output-style.md: adds FOCUS 1.3+ terminology preferences
  for ServiceProviderName, HostProviderName, and ContractApplied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HubsFocusSchemas.Tests.ps1: 108 Pester tests asserting the
  FOCUS 1.3 and 1.4-preview schema additions land where expected:
  Costs_raw additions, ContractCommitment_raw definition with
  both 1.3 and 1.4 columns, presence of versioned transforms
  and final tables in IngestionSetup_v1_3 / v1_4, presence of
  versioned hub functions in HubSetup_v1_3 / v1_4, the union
  arms in Costs_v1_3 and Costs_v1_4, the Latest aliases pinned
  to v1_3 (not preview v1_4), and the Bicep / build-config
  wiring.
- changelog.md: adds v15.0.0 hub entries describing the FOCUS
  1.3 GA and 1.4-preview schema additions, the new
  ContractCommitment dataset, the unversioned alias retarget,
  and the upgrade-procedure refresh. Adds a plugin entry for
  #2119. Updates ms.date to today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidates FOCUS 1.3 and 1.4 column additions into a single v1_4 ingestion
setup file. Removes the intermediate v1_3 file, adds ContractCommitment_raw
table definition, and updates app.bicep and .build.config to reference v1_4.

Co-Authored-By: Claude <noreply@anthropic.com>
Renames HubSetup_v1_3.kql to HubSetup_v1_4.kql, consolidating all hub
functions (including ContractCommitment) for FOCUS 1.4 GA. Updates
HubSetup_Latest.kql to alias all unversioned functions to v1_4. Updates
app.bicep and .build.config to reference v1_4.

Co-Authored-By: Claude <noreply@anthropic.com>
Updates all FOCUS version references from 1.3/1.4-preview to v1_4 GA.
Removes intermediate v1_3 function references. Marks ContractCommitment
as FOCUS 1.4+ (not 1.3+). Removes preview designations for v1_4.

Co-Authored-By: Claude <noreply@anthropic.com>
Updates Pester tests to validate v1_4 (not v1_3) schema files, removes
tests for the eliminated intermediate v1_3 schema, and corrects
HubSetup_Latest assertions to expect _v1_4() aliases. Updates changelog
to reflect FOCUS 1.4 GA release without preview designation.

Co-Authored-By: Claude <noreply@anthropic.com>
- Pluralize ContractCommitments_raw / _transform_v1_4 / _final_v1_4 / mapping / update policy
- Add BillingPeriods_raw + IngestionSetup_v1_4 transforms/finals/policies (FOCUS 1.4 supplemental dataset)
- Add InvoiceDetails_raw + IngestionSetup_v1_4 transforms/finals/policies (FOCUS 1.4 supplemental dataset)
- Add 12 ContractCommitment* per-row columns + CommitmentProgramEligibilityDetails + InvoiceDetailId to Costs_raw, Costs_final_v1_4, and Costs_transform_v1_4
- Fix x_Source* annotations on ContractCommitments_raw to Hubs v1_4+
- Update Costs_raw ProviderName/PublisherName annotations to FOCUS 0.5-1.3 (removed in 1.4; kept for back compat)
- Update Costs_raw Region annotation: deprecated -> removed
- File #2159 for the FOCUS 1.4 native-columns architecture research

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
- Rename ContractCommitment_v1_4() -> ContractCommitments_v1_4() (and update folder label)
- Add BillingPeriods_v1_4() and InvoiceDetails_v1_4() hub functions
- Add BillingPeriods(), ContractCommitments() (renamed), and InvoiceDetails() unversioned aliases in HubSetup_Latest.kql
- Add CommitmentProgramEligibilityDetails + 12 ContractCommitment* + InvoiceDetailId column defaults to Costs_final_v1_0 and Costs_final_v1_2 union arms (back-compat shims) and add them to the project list

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…-phase6-plugin

# Conflicts:
#	src/templates/finops-hub/.build.config
#	src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/app.bicep
#	src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_4.kql
#	src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_RawTables.kql
#	src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_4.kql
- Rename ContractCommitment() -> ContractCommitments() in skill references and agent docs
- Add BillingPeriods() and InvoiceDetails() to the function list and dedicated agent doc sections
- Mention the new FOCUS 1.4 per-row columns (CommitmentProgramEligibilityDetails, 12 ContractCommitment* columns, InvoiceDetailId)
- Update output-style guidance to reference all three supplemental datasets

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A merge artifact from rebasing on the v1_3 ancestry left a singular
ContractCommitment() function referencing ContractCommitment_v1_3() in
HubSetup_Latest.kql. v1_3 was eliminated and the dataset was pluralized
to ContractCommitments_v1_4(); the orphan would fail to load on hub
deployment. Remove it.

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Tests:
- Pluralize ContractCommitments_raw / _final_v1_4 / _v1_4 assertions
- Assert new BillingPeriods_raw + InvoiceDetails_raw datasets exist with their FOCUS 1.4 columns
- Assert new BillingPeriods_v1_4(), InvoiceDetails_v1_4(), ContractCommitments_v1_4() hub functions
- Assert HubSetup_Latest aliases for BillingPeriods(), ContractCommitments(), InvoiceDetails()
- Assert Costs_raw and Costs_final_v1_4 include the 14 new FOCUS 1.4 columns
- Assert NO singular ContractCommitment_raw / ContractCommitment_v1_4 / ContractCommitment() exists
- Assert Costs_raw keeps removed ProviderName/PublisherName/Region for back compat

Changelog:
- Expand v15.0.0 entry with the 14 new FOCUS 1.4 columns and the three new supplemental datasets (ContractCommitments, BillingPeriods, InvoiceDetails)
- Update plugin entry to list ContractCommitments() / BillingPeriods() / InvoiceDetails()

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Adds two hub-toolkit enrichment columns to the Recommendations dataset in
the FOCUS 1.4 hub schema, normalized to a fixed hub taxonomy so reports
can group recommendations consistently across providers.

- IngestionSetup_v1_4.kql: normalize x_RecommendationCategory into the
  5 top-level values (Cost, Operational Excellence, Performance,
  Reliability, Security); map Azure Advisor's HighAvailability -> Reliability;
  derive x_RecommendationSubcategory from the documented taxonomy with
  'Other' as the fallback. Add both columns to Recommendations_final_v1_4.
- HubSetup_v1_4.kql: include both columns in the Recommendations_v1_4()
  project list.
- data-model.md: document the allowed values for both columns and the
  normalization behavior.
- HubsFocusSchemas.Tests.ps1: assert columns exist in the final table,
  the transform projects them, and the case() expression covers all
  5 top-level categories.
- changelog.md: note the new enrichment columns under FinOps hubs v15.0.0.

Co-Authored-By: Michael Flanakin <flanakin@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — "Pick one" resolutions (Low Utilization, Idle Resources, Unused Capacity)

The PR resolved both of the user's open "pick one" questions and the standalone-Unused-Capacity question. The picks are reasonable but the boundary between Low Utilization and Idle Resources is the single biggest ambiguity in the entire Cost taxonomy and deserves an explicit definition, not just a label list.

Low Utilization (over Underused Resources) — defensible. Low Utilization is closer to the FinOps Framework's "Workload Optimization" capability vocabulary and matches Azure Advisor's own "Low utilization virtual machines" recommendation string. Underused Resources reads as a generic noun phrase that overlaps Right-Sizing semantically. Good pick.

Idle Resources (over Unused Resources) — defensible. Idle Resources is the more common industry term (Azure Advisor "idle public IP address", AWS Trusted Advisor "Idle Load Balancers", FOCUS-aligned terminology). The parenthetical "(includes orphaned)" in the docs is helpful.

Folding Unused Capacity into Idle Resources — this is where I'd push back. The folding rationale ("a separate bucket would have produced overlap") is reasonable in spirit but the resulting label is now overloaded across three distinct cost levers:

  1. Orphaned resources (unattached disks, unassociated public IPs) — a hygiene/cleanup action
  2. Idle running compute (VM at 2% CPU for 30 days) — a stop/deallocate action
  3. Unused reserved/committed capacity (paid-for RI hours not consumed, unused savings plan commitment) — a commitment-management action

Item 3 is fundamentally different from 1 and 2 — the user has already paid for it, the resource isn't actually idle from the provider's POV, and the remediation lives in commitment portfolio management, not resource cleanup. Lumping unused commitment hours under Idle Resources will make reports misleading and conflict with how FinOps practitioners think about rate vs. usage optimization.

Recommendation: Either (a) keep Idle Resources covering #1 and #2 only and route "unused commitment hours" into Commitment Discount Utilization (which already exists and is the correct semantic home), or (b) document explicitly in data-model.md that "unused capacity" here means unattached/orphaned provisioned capacity, not unused committed capacity. Today's phrasing "includes unused capacity and orphaned resources" reads as if all three cases land in Idle Resources, which would degrade signal quality.

🤖 [AI][Claude Code] Taxonomy review

@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — Cross-category overlap and routing ambiguity

A single subcategory list per parent category is clean, but several subcategories sit on category boundaries where ingestion will have to make a judgment call. These need either (a) tie-breaker rules documented next to the taxonomy, or (b) renames that make the boundary obvious.

Right-Sizing (Cost) vs Resource Sizing (Performance) — biggest concern. The same recommendation ("upgrade to a larger SKU because the workload is throttling") could land in either. In practice, Azure Advisor surfaces sizing recs under both Cost (downsize to save) and Performance (upsize to relieve pressure). The current labels don't make the direction explicit. Suggest renaming Performance's Resource Sizing to Performance Sizing or Capacity Sizing to signal "size up for SLO" vs "size down for cost".

Scaling and Capacity (Performance) vs Autoscaling (Cost) vs Capacity (Reliability) — three scaling buckets across three categories. The intended split is presumably: Autoscaling = cost lever (scale down when idle), Scaling and Capacity = perf lever (scale out for throughput), Capacity = reliability lever (headroom for failover/spike). That's defensible but invisible to a reader of the label list. At minimum, rename Reliability's Capacity to Capacity Headroom or Capacity Reservation to disambiguate from Performance's Scaling and Capacity.

Quotas and Limits (OpEx) vs Capacity (Reliability) — quota exhaustion is simultaneously an operational hygiene issue (raise the quota) and a reliability issue (failover region runs out of vCPUs). Suggest documenting that quota recs always land in OpEx, and Capacity (Reliability) is reserved for consumed-vs-available capacity recs.

Backup Configuration (Reliability) vs Data Protection (Security) — overlap is real. Backup encryption, immutability, ransomware vaults, and cross-region backup copies all read as both. Suggest documenting that backup-availability recs (RPO, retention, geo-redundancy) land under Reliability, while backup-confidentiality recs (encryption, access policies on backup vaults) land under Security.

Resource Configuration (Performance) vs Resource Consistency (OpEx) — these labels are nearly synonymous to a non-expert reader. Consider renaming Performance's Resource Configuration to Performance Configuration or Tuning Configuration. As written, an Advisor rec like "enable accelerated networking" could land in either.

Service Retirement (Reliability) vs Operational Hygiene (OpEx) — service retirement (e.g., Classic VMs going EOL) is operational debt as much as a reliability risk. Defensible to keep under Reliability since the consequence is downtime, but worth noting in the docs.

Without disambiguation rules, two Hub deployments ingesting the same Advisor stream could classify identical recommendations differently depending on which case() branch fires first. That kills cross-tenant report comparability — the explicit goal of having a fixed taxonomy.

🤖 [AI][Claude Code] Taxonomy review

@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — Missing subcategories

The Cost category is the most consequential one for a FinOps Hub, and several very common cost-recommendation types have no home in the current taxonomy and would all fall into Other. That defeats the purpose of having a fixed taxonomy for reporting.

Critical gaps (would already be hit by Azure Advisor, Cost Management, and partner tools today):

  1. License optimization — Azure Hybrid Benefit, BYOL, SQL Server license mobility, Windows Server license sync. Azure Advisor explicitly surfaces "Consider Azure Hybrid Benefit" recs. These are neither right-sizing nor commitment discounts. Suggest adding License Optimization under Cost.

  2. Storage tier / lifecycle — recommendations to move blobs to cool/archive, enable lifecycle management, delete old snapshots. This is a huge category in practice and doesn't map cleanly to Idle Resources (the data isn't idle, it's just on the wrong tier) or Right-Sizing. Suggest adding Storage Tiering or Storage Lifecycle under Cost.

  3. Data transfer / egress — recommendations to use Private Link, ExpressRoute Direct, PoP-local egress, or to relocate workloads to reduce inter-region transfer cost. Today these would fall to Region Placement or Other. Region Placement is overloaded if it's also the home for egress optimization. Suggest adding Data Transfer or Network Cost under Cost.

  4. Tagging / cost allocation hygiene — recs to apply missing tags, normalize tag values, or enforce allocation tagging. These are technically Cost (they affect allocation) but operationally OpEx (they're a hygiene action). Suggest adding Cost Allocation under Cost, or explicitly documenting that tagging recs land under Governance and Policy (OpEx).

Important gaps:

  1. Marketplace / third-party SaaS — Azure Marketplace spend is a growing category; recs around marketplace subscription cleanup, plan downgrades, BYOL on marketplace SKUs have no home. Suggest Marketplace Optimization under Cost.

  2. Free tier / trial expiration — particularly relevant for sandbox/dev workloads moving off free credits. Could fit under Operational Hygiene (OpEx) but worth calling out.

  3. Currency / billing-currency hedging — niche but real for enterprises with multi-currency MCA. Probably acceptable to leave in Other.

Cross-cloud / multi-cloud allocation — the user's question implied this is in scope. If the hub will normalize AWS Cost Explorer / GCP Recommender recs (per the FOCUS framing), categories like "Reserved Instance modification" (AWS) and "Committed Use Discount" (GCP) already map cleanly to existing Cost subcategories — that's fine. But "Savings Plans coverage gap", "Compute Optimizer rightsizing", and "GCP Recommender idle VM" need test coverage to confirm the mapping holds. Worth adding test cases.

The taxonomy currently captures the FinOps Framework's "Workload Optimization" and "Rate Optimization" capabilities reasonably well but misses most of "Architecting for Cloud" (license, tier, transfer) and parts of "Allocation".

🤖 [AI][Claude Code] Taxonomy review

@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — Naming consistency and label hygiene

The taxonomy is mostly noun-phrased and Title-Cased, but a few inconsistencies will create friction for downstream consumers (filters, dropdowns, KQL in (…) lists).

Hyphen vs space inconsistency:

  • Right-Sizing (Cost) is hyphenated.
  • Resource Sizing (Performance) is not.
  • SKU Modernization, Resource Configuration, Resource Consistency are space-separated.

Either keep the hyphen for Right-Sizing (common industry term, defensible) or normalize to Right Sizing. Today's mix means an end user filtering by "sizing" recs has to remember which one is hyphenated. If you keep the hyphen, document why (it's a single compound term) so future additions follow the same rule.

& vs and:

  • Identity & Access (Security) uses an ampersand.
  • Every other multi-word label uses and (Automation and Process, Governance and Policy, Quotas and Limits, Scaling and Capacity, Throughput and Latency, Service Selection and Architecture).

This is the most jarring inconsistency. Suggest renaming to Identity and Access for consistency. Bonus: it matches the more common framework name "Identity and Access Management (IAM)".

Verb vs noun phrasing: mostly noun-phrased, which is good. The two outliers worth checking:

  • Threat Detection (Security) — noun phrase, fine.
  • Negotiated Discount Enablement (Cost) — wordy and ambiguous. "Enablement" implies the rec is to enable a negotiated discount, but in practice these recs detect that a customer is not benefiting from a negotiated discount they already have. Suggest Negotiated Discount or Negotiated Pricing — drop Enablement.

CapitalizationTitle Casing is used throughout, which is correct for a closed enum. Confirm the KQL case() expression is case-sensitive (it appears to be — in ('Cost', ...) not =~). That means a producer writing cost instead of Cost would fall through to the default. That's probably intentional (forces normalization on ingestion) but worth documenting.

"Other" placementOther appears in the in-list of valid subcategories and is the explicit fallback. That's fine but means a source system could legitimately set x_RecommendationSubcategory = 'Other' and have it pass through, which is indistinguishable from "we didn't have a mapping". Consider documenting that Other from an upstream source is treated identically to the default fallback (no information loss, but no provenance either).

High Availaibility typo — fixed in this PR. Good.

🤖 [AI][Claude Code] Taxonomy review

@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — KQL mapping logic and default behavior

The case() expression in Recommendations_transform_v1_4() does the right thing structurally but has a few semantic issues worth tightening before this becomes the canonical normalization path.

Asymmetric defaults — category vs subcategory:

  • x_RecommendationCategory defaults to empty string ('') for unknown values.
  • x_RecommendationSubcategory defaults to 'Other' for unknown values.

This asymmetry is documented in the PR description, but it has a usability consequence: a row can legitimately have x_RecommendationCategory = '' and x_RecommendationSubcategory = 'Commitment Discount Coverage' (if x_SourceType == 'ReservationRecommendations' is false but the upstream value happened to be in the allow-list). That's an inconsistent state that downstream reports won't expect.

Suggest one of:

  • (a) Always populate both. If category is unknown, force subcategory to Other (don't let an allow-listed subcategory leak through under an empty category).
  • (b) Validate the (category, subcategory) pair against the documented hierarchy. Today a row could have Category = Cost, Subcategory = Threat Detection if the upstream details object lies about both. Worth a parent-child validation pass.

HighAvailabilityReliability mapping is correct — confirmed against Azure Advisor's documented Category enum (Cost, HighAvailability, OperationalExcellence, Performance, Security). Good.

Reservation default to Cost — correct. Reservation recommendations from Microsoft.Consumption/reservationRecommendations don't carry a Category, and they're definitionally cost recs.

Hard-coded reservation subcategory — the fallback x_SourceType == 'ReservationRecommendations'Commitment Discount Coverage is correct for Azure RIs but assumes the source is always a coverage rec, never a utilization rec. Cost Management does surface both:

  • reservationRecommendations API → coverage (which SKU to buy)
  • reservationDetails / utilization API → utilization (what % of purchased RIs are being consumed)

If the hub ever ingests reservation utilization recs under the same x_SourceType, they would be miscategorized as Coverage when they should be Commitment Discount Utilization. Worth adding a discriminator (e.g., based on x_RecommendationDetails shape) or splitting x_SourceType values.

=~ (case-insensitive equality) for Advisor values — fine, but redundant since the documented Advisor enum is fixed PascalCase. If you want to harden against unexpected casing from undocumented sources, =~ is a small cost. If you want strict provenance, == would be more honest. Either is defensible; just be consistent.

No mapping for Microsoft Defender for Cloud / Microsoft Sentinel recs — these flow through a different API and category schema (Microsoft.Security/assessments). If the hub ever ingests them, the case() would drop them all to empty-category. Worth a follow-up to add a MicrosoftDefenderForCloudSecurity branch when that source is wired up.

Subcategory case() ordering — the allow-list check fires first, then the reservation fallback, then Other. That means a reservation rec whose upstream details happen to set x_RecommendationSubcategory to a different valid value (e.g., Idle Resources) would pass through as Idle Resources, not Commitment Discount Coverage. Probably intentional (trust the upstream) but worth a comment in the KQL to make the precedence explicit.

🤖 [AI][Claude Code] Taxonomy review

@flanakin

Copy link
Copy Markdown
Collaborator Author

Taxonomy review — Documentation completeness and canonical-vs-open status

The taxonomy is documented in data-model.md as a bulleted list, which is fine for human reading but insufficient for a fixed taxonomy meant to drive cross-tenant reporting.

Is this a closed enum or an open vocabulary? The PR description says "fixed hub taxonomy" but the docs don't explicitly say "this list is canonical and will not change without a schema version bump". Without that statement:

  • A consumer building a Power BI report can't know whether to hard-code a slicer against these values.
  • A partner ingesting recs into the hub can't know whether to invest in mapping to this list vs. waiting for it to stabilize.
  • A future contributor doesn't know whether adding a new subcategory is a breaking change.

Suggest adding to data-model.md:

The lists below are canonical for the FOCUS 1.4 hub schema. New values may be added in a future schema version; existing values will not be renamed or removed without a schema version bump.

No queryable form — the taxonomy lives in (a) the KQL case() expression, (b) the docs markdown, and (c) the Pester test. These three sources are not linked; if one drifts from another, no automation catches it. Worth considering:

  • A Recommendations_taxonomy_v1_4() KQL function that returns the (category, subcategory) pairs as rows. Consumers could join against it to validate or to populate UI dropdowns.
  • Or an open-data CSV under src/open-data/ (the repo already has this pattern for regions and services). Then docs and KQL both derive from a single source.

Today, a Power BI author building a Recommendations report has to either (a) re-type the list from the docs, or (b) summarize by x_RecommendationSubcategory against live data, which won't surface taxonomy values that haven't been seen yet.

Mapping table to source values is missing — the docs say "Azure Advisor's HighAvailabilityReliability" as an example but don't document the full mapping. A consumer who sees x_RecommendationCategory = 'Reliability' and wants to know which Advisor categories that includes has to read the KQL. Worth adding a small table in data-model.md:

Source Source value Hub category
Azure Advisor Cost Cost
Azure Advisor HighAvailability Reliability
Azure Advisor OperationalExcellence Operational Excellence
Azure Advisor Performance Performance
Azure Advisor Security Security
Cost Management ReservationRecommendations Cost

This becomes essential when you add a second source (Defender for Cloud, AWS Trusted Advisor, GCP Recommender).

Changelog entry — the entry calls out the columns but not the taxonomy decisions (Low Utilization vs Underused, Idle Resources covers unused capacity, etc.). Those decisions are the load-bearing part of this PR and deserve a one-line callout so a v15 consumer doesn't have to read the PR to understand the breaking semantic.

🤖 [AI][Claude Code] Taxonomy review

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