ValueOpportunity/Measure-ValueOpportunity.ps1
|
function Measure-ValueOpportunity { <# .SYNOPSIS Merges license utilization, feature adoption, and readiness into a unified analysis. .DESCRIPTION Produces overall adoption percentage, category breakdown, phased roadmap, and gap matrix from the three Value Opportunity collector outputs. .PARAMETER LicenseUtilization Array of PSCustomObjects from Get-LicenseUtilization. .PARAMETER FeatureAdoption Array of PSCustomObjects from Get-FeatureAdoption. .PARAMETER FeatureReadiness Array of PSCustomObjects from Get-FeatureReadiness. .PARAMETER FeatureMap Parsed sku-feature-map.json object. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [PSCustomObject[]]$LicenseUtilization, [Parameter(Mandatory)] [PSCustomObject[]]$FeatureAdoption, [Parameter(Mandatory)] [PSCustomObject[]]$FeatureReadiness, [Parameter(Mandatory)] $FeatureMap ) # Build lookup tables keyed by FeatureId $licenseLookup = @{} foreach ($item in $LicenseUtilization) { $licenseLookup[$item.FeatureId] = $item } $adoptionLookup = @{} foreach ($item in $FeatureAdoption) { $adoptionLookup[$item.FeatureId] = $item } $readinessLookup = @{} foreach ($item in $FeatureReadiness) { $readinessLookup[$item.FeatureId] = $item } $featureMapLookup = @{} foreach ($item in $FeatureMap.features) { $featureMapLookup[$item.featureId] = $item } # Build category name lookup from FeatureMap $categoryNameLookup = @{} foreach ($cat in $FeatureMap.categories) { $categoryNameLookup[$cat.id] = $cat.name } # Identify licensed feature IDs $licensedFeatureIds = @($LicenseUtilization | Where-Object { $_.IsLicensed -eq $true } | ForEach-Object { $_.FeatureId }) $licensedFeatureCount = $licensedFeatureIds.Count # Count adopted and partial among licensed features $adoptedFeatureCount = 0 $partialFeatureCount = 0 $gapCount = 0 foreach ($featureId in $licensedFeatureIds) { $adoption = $adoptionLookup[$featureId] if ($adoption) { switch ($adoption.AdoptionState) { 'Adopted' { $adoptedFeatureCount++ } 'Partial' { $partialFeatureCount++ } default { $gapCount++ } } } else { $gapCount++ } } # Overall adoption percentage if ($licensedFeatureCount -eq 0) { $overallAdoptionPct = 0 } else { $overallAdoptionPct = [int][Math]::Round(($adoptedFeatureCount + $partialFeatureCount) / $licensedFeatureCount * 100, 0, [MidpointRounding]::AwayFromZero) } # Category breakdown - group by Category from FeatureAdoption $categoryGroups = @{} foreach ($adoption in $FeatureAdoption) { $category = $adoption.Category if (-not $categoryGroups.ContainsKey($category)) { $categoryGroups[$category] = @{ Category = $category Licensed = 0 Adopted = 0 Partial = 0 NotAdopted = 0 Unknown = 0 } } $featureId = $adoption.FeatureId $license = $licenseLookup[$featureId] $isLicensed = $license -and $license.IsLicensed -eq $true if ($isLicensed) { $categoryGroups[$category].Licensed++ switch ($adoption.AdoptionState) { 'Adopted' { $categoryGroups[$category].Adopted++ } 'Partial' { $categoryGroups[$category].Partial++ } 'NotAdopted' { $categoryGroups[$category].NotAdopted++ } 'Unknown' { $categoryGroups[$category].Unknown++ } default { $categoryGroups[$category].Unknown++ } } } } # Compute per-category percentage $categoryBreakdown = @() foreach ($entry in $categoryGroups.Values) { if ($entry.Licensed -eq 0) { $entry['Pct'] = 0 } else { $entry['Pct'] = [int][Math]::Round(($entry.Adopted + $entry.Partial) / $entry.Licensed * 100, 0, [MidpointRounding]::AwayFromZero) } $categoryBreakdown += $entry } # Sort category breakdown by name for deterministic output $categoryBreakdown = @($categoryBreakdown | Sort-Object { $_.Category }) # Roadmap - licensed features with AdoptionState NotAdopted or Unknown, grouped by EffortTier $roadmap = @{ 'Quick Win' = @() 'Medium' = @() 'Strategic' = @() } foreach ($featureId in $licensedFeatureIds) { $adoption = $adoptionLookup[$featureId] if (-not $adoption) { continue } if ($adoption.AdoptionState -in @('NotAdopted', 'Unknown')) { $readiness = $readinessLookup[$featureId] $mapEntry = $featureMapLookup[$featureId] $effortTier = if ($readiness) { $readiness.EffortTier } else { 'Strategic' } $mergedObj = [PSCustomObject]@{ FeatureId = $featureId FeatureName = if ($adoption.FeatureName) { $adoption.FeatureName } else { '' } Category = if ($adoption.Category) { $adoption.Category } else { '' } AdoptionScore = if ($adoption.AdoptionScore) { $adoption.AdoptionScore } else { 0 } ReadinessState = if ($readiness) { $readiness.ReadinessState } else { 'Unknown' } Blockers = if ($readiness) { $readiness.Blockers } else { '' } EffortTier = $effortTier LearnUrl = if ($readiness) { $readiness.LearnUrl } else { '' } } if ($roadmap.ContainsKey($effortTier)) { $roadmap[$effortTier] += @($mergedObj) } else { $roadmap[$effortTier] = @($mergedObj) } } } # Gap matrix - same as category breakdown but without Pct $gapMatrix = @() foreach ($entry in $categoryBreakdown) { $gapMatrix += @{ Category = $entry.Category Adopted = $entry.Adopted Partial = $entry.Partial NotAdopted = $entry.NotAdopted Unknown = $entry.Unknown } } # Not-licensed features $notLicensedFeatures = @() foreach ($item in $LicenseUtilization) { if ($item.IsLicensed -eq $false) { $featureId = $item.FeatureId $adoption = $adoptionLookup[$featureId] $readiness = $readinessLookup[$featureId] $mapEntry = $featureMapLookup[$featureId] $notLicensedFeatures += [PSCustomObject]@{ FeatureId = $featureId FeatureName = if ($adoption) { $adoption.FeatureName } else { '' } Category = if ($adoption) { $adoption.Category } else { '' } EffortTier = if ($readiness) { $readiness.EffortTier } else { '' } RequiredServicePlans = if ($mapEntry) { $mapEntry.requiredServicePlans } else { @() } } } } return @{ OverallAdoptionPct = $overallAdoptionPct LicensedFeatureCount = $licensedFeatureCount AdoptedFeatureCount = $adoptedFeatureCount PartialFeatureCount = $partialFeatureCount GapCount = $gapCount CategoryBreakdown = $categoryBreakdown Roadmap = $roadmap GapMatrix = $gapMatrix NotLicensedFeatures = $notLicensedFeatures } } |