← Files Power BI DesktopARCHIVED FILE
scripts/New-PowerBIUsageTrustMatrix.ps1
6.53 KB · Oct 2, 2026 · 00:32 UTC
param(
[string]$Path = ".",
[string]$UsageSignalsPath,
[string]$OutputPath,
[switch]$Json
)
$ErrorActionPreference = 'Stop'
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$resolved = (Resolve-Path -LiteralPath $Path).Path
function Read-JsonFile {
param([string]$LiteralPath)
if ($LiteralPath -and (Test-Path -LiteralPath $LiteralPath)) {
$raw = Get-Content -Raw -LiteralPath $LiteralPath
if ($raw.Trim().StartsWith('{') -or $raw.Trim().StartsWith('[')) {
return ($raw | ConvertFrom-Json)
}
}
$null
}
function Normalize-Key {
param([string]$Value)
if (-not $Value) { return '' }
($Value.ToLowerInvariant() -replace '[^a-z0-9]', '')
}
$trust = & (Join-Path $scriptRoot 'New-PowerBIKpiTrustScore.ps1') -Path $resolved -Json | ConvertFrom-Json
if (@($trust.metrics).Count -eq 0) {
$liveCatalogPath = Join-Path $resolved 'live-metric-catalog.json'
if (Test-Path -LiteralPath $liveCatalogPath) {
$liveCatalog = Get-Content -Raw -LiteralPath $liveCatalogPath | ConvertFrom-Json
$liveMetrics = foreach ($metric in @($liveCatalog.metrics)) {
$score = 100
$deductions = New-Object System.Collections.Generic.List[string]
if (@($metric.risks).Count -gt 0) { $score -= 25; $deductions.Add('DAX risk detected') }
if ($metric.owner -match 'TODO') { $score -= 15; $deductions.Add('Metric owner missing') }
if ($metric.businessDefinition -match 'TODO') { $score -= 15; $deductions.Add('Business definition missing') }
$score = [math]::Max(0, [int]$score)
[pscustomobject]@{
name = $metric.name
table = $metric.table
trustScore = $score
trustBand = $(if ($score -ge 80) { 'High' } elseif ($score -ge 60) { 'Medium' } else { 'Low' })
releaseUse = $(if ($score -ge 80) { 'Approved candidate after business sign-off.' } elseif ($score -ge 60) { 'Use with warning and validation note.' } else { 'Do not use for executive release without remediation.' })
}
}
$trust = [pscustomobject]@{
schema = 'codex.powerbi.kpiTrustScore.liveCatalogFallback.v1'
metrics = @($liveMetrics)
overallTrustScore = if (@($liveMetrics).Count -gt 0) { [int]((@($liveMetrics | Select-Object -ExpandProperty trustScore) | Measure-Object -Average).Average) } else { 0 }
}
}
}
$gate = & (Join-Path $scriptRoot 'New-PowerBITrustReleaseGate.ps1') -Path $resolved -Json | ConvertFrom-Json
$service = & (Join-Path $scriptRoot 'New-PowerBIServiceScanner.ps1') -Path $resolved -Json | ConvertFrom-Json
$usage = Read-JsonFile -LiteralPath $UsageSignalsPath
if (-not $usage) {
$usage = & (Join-Path $scriptRoot 'Import-PowerBIUsageSignals.ps1') -Path $resolved -Json | ConvertFrom-Json
}
$usageByKey = @{}
foreach ($item in @($usage.reportUsage)) {
$key = Normalize-Key $item.reportName
if ($key) { $usageByKey[$key] = $item }
}
$maxUsage = @($usage.reportUsage | Sort-Object @{ Expression = 'viewCount'; Descending = $true }, @{ Expression = 'activityCount'; Descending = $true } | Select-Object -First 1)
$matrixItems = foreach ($metric in @($trust.metrics)) {
$metricKey = Normalize-Key $metric.name
$usageItem = $null
foreach ($key in $usageByKey.Keys) {
if ($key.Contains($metricKey) -or $metricKey.Contains($key)) {
$usageItem = $usageByKey[$key]
break
}
}
if (-not $usageItem -and $maxUsage) { $usageItem = $maxUsage }
$activity = if ($usageItem) { [int]$usageItem.activityCount } else { 0 }
$views = if ($usageItem) { [int]$usageItem.viewCount } else { 0 }
$usageBand = if ($views -ge 100 -or $activity -ge 50) { 'High' } elseif ($views -ge 10 -or $activity -ge 10) { 'Medium' } elseif ($activity -gt 0 -or $views -gt 0) { 'Low' } else { 'Unknown' }
$priority = if ($metric.trustScore -lt 60 -and $usageBand -eq 'High') { 'P0' } elseif ($metric.trustScore -lt 60 -and $usageBand -in @('Medium', 'Low', 'Unknown')) { 'P1' } elseif ($metric.trustScore -lt 80 -and $usageBand -eq 'High') { 'P1' } else { 'P2' }
[pscustomobject]@{
metricName = $metric.name
table = $metric.table
trustScore = $metric.trustScore
trustBand = $metric.trustBand
usageBand = $usageBand
activityCount = $activity
viewCount = $views
matchedUsageItem = if ($usageItem) { $usageItem.reportName } else { $null }
priority = $priority
releaseUse = $metric.releaseUse
}
}
$highUsageLowTrust = @($matrixItems | Where-Object { $_.trustScore -lt 60 -and $_.usageBand -in @('High', 'Medium') } | Sort-Object priority, trustScore)
if ($highUsageLowTrust.Count -eq 0) {
$highUsageLowTrust = @($matrixItems | Where-Object { $_.trustScore -lt 60 } | Sort-Object trustScore | Select-Object -First 10)
}
$overallPriority = if (@($highUsageLowTrust | Where-Object priority -eq 'P0').Count -gt 0 -or $gate.decision -eq 'No-Go') { 'P0' } elseif ($gate.decision -eq 'Warn' -or $highUsageLowTrust.Count -gt 0) { 'P1' } else { 'P2' }
$result = [pscustomobject]@{
schema = 'codex.powerbi.usageTrustMatrix.v1'
generated = (Get-Date).ToString('s')
source = $resolved
usageSignalsPath = $UsageSignalsPath
releaseDecision = $gate.decision
serviceFindingCount = $service.findingCount
usageStatus = $usage.status
priority = $overallPriority
metricCount = @($matrixItems).Count
highUsageLowTrustCount = $highUsageLowTrust.Count
highUsageLowTrust = @($highUsageLowTrust)
matrixItems = @($matrixItems)
}
if ($Json) {
$text = $result | ConvertTo-Json -Depth 8
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $text -Encoding UTF8 }
$text
return
}
$lines = @(
'# Power BI Usage Trust Matrix',
'',
"Priority: $overallPriority",
"Release decision: $($gate.decision)",
"Usage status: $($usage.status)",
'',
'## High usage / low trust'
) + @($highUsageLowTrust | ForEach-Object { "- [$($_.priority)] $($_.metricName): trust=$($_.trustScore), usage=$($_.usageBand), views=$($_.viewCount), activities=$($_.activityCount)" })
if ($highUsageLowTrust.Count -eq 0) {
$lines += '- No high-usage low-trust KPI candidates found.'
}
$content = ($lines -join [Environment]::NewLine) + [Environment]::NewLine
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $content -Encoding UTF8 }
$content
SHA-256: 2a4c9bb78a5fa73a31de2311256ff45a7411f97b0fad4e9461d31681fdd28015