← Files Power BI DesktopARCHIVED FILE
scripts/New-PowerBIEvidenceGraph.ps1
6.12 KB · Oct 2, 2026 · 00:32 UTC
param(
[string]$Path = ".",
[string]$ReviewDirectory,
[string]$OutputPath,
[switch]$Json
)
$ErrorActionPreference = 'Stop'
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = (Resolve-Path -LiteralPath $Path).Path
function Read-JsonArtifact {
param([string]$FilePath)
if (-not $FilePath -or -not (Test-Path -LiteralPath $FilePath)) { return $null }
try { return Get-Content -Raw -LiteralPath $FilePath | ConvertFrom-Json } catch { return $null }
}
function Add-Node {
param($List, [string]$Id, [string]$Type, [string]$Label, [string]$Status, [string]$Source)
$List.Add([pscustomobject]@{ id = $Id; type = $Type; label = $Label; status = $Status; source = $Source }) | Out-Null
}
function Add-Edge {
param($List, [string]$From, [string]$To, [string]$Relation)
$List.Add([pscustomobject]@{ from = $From; to = $To; relation = $Relation }) | Out-Null
}
$catalog = & (Join-Path $scriptRoot 'New-PowerBIMetricCatalog.ps1') -Path $root -Json | ConvertFrom-Json
$trust = & (Join-Path $scriptRoot 'New-PowerBIKpiTrustScore.ps1') -Path $root -Json | ConvertFrom-Json
$deps = & (Join-Path $scriptRoot 'New-PowerBIDependencyGraph.ps1') -Path $root -Json | ConvertFrom-Json
$semantic = & (Join-Path $scriptRoot 'Invoke-PowerBISemanticTestRunner.ps1') -Path $root -Json | ConvertFrom-Json
$visualImpact = & (Join-Path $scriptRoot 'New-PowerBIVisualMeasureImpactMap.ps1') -Path $root -Json | ConvertFrom-Json
$nodes = New-Object System.Collections.Generic.List[object]
$edges = New-Object System.Collections.Generic.List[object]
Add-Node -List $nodes -Id 'model' -Type 'Model' -Label (Split-Path -Leaf $root) -Status 'Source' -Source $root
foreach ($metric in @($catalog.metrics)) {
$metricId = 'metric:' + $metric.id
$trustMetric = @($trust.metrics | Where-Object { $_.name -eq $metric.name } | Select-Object -First 1)
$status = if ($trustMetric) { $trustMetric.trustBand } else { $metric.riskLevel }
Add-Node -List $nodes -Id $metricId -Type 'Metric' -Label $metric.name -Status $status -Source $metric.source
Add-Edge -List $edges -From 'model' -To $metricId -Relation 'contains'
}
foreach ($edge in @($deps.edges)) {
Add-Edge -List $edges -From ('metric:' + $edge.fromId) -To ('metric:' + $edge.toId) -Relation 'dependsOn'
}
foreach ($test in @($semantic.tests)) {
$testLabelCandidates = @($test.measure, $test.measureName, $test.metricName, $test.name, $test.id) | Where-Object { $_ }
$testLabel = if ($testLabelCandidates.Count -gt 0) { [string]$testLabelCandidates[0] } else { 'Semantic test' }
$testStatusCandidates = @($test.result, $test.status) | Where-Object { $_ }
$testStatus = if ($testStatusCandidates.Count -gt 0) { [string]$testStatusCandidates[0] } else { 'Unknown' }
$testId = 'semantic-test:' + ($testLabel -replace '[^A-Za-z0-9_-]', '-')
Add-Node -List $nodes -Id $testId -Type 'SemanticTest' -Label $testLabel -Status $testStatus -Source 'semantic-tests'
$measureCandidates = @($test.measure, $test.measureName, $test.metricName) | Where-Object { $_ }
$measure = if ($measureCandidates.Count -gt 0) { [string]$measureCandidates[0] } else { $null }
if ($measure) {
$metric = @($catalog.metrics | Where-Object { $_.name -eq $measure } | Select-Object -First 1)
if ($metric) { Add-Edge -List $edges -From ('metric:' + $metric.id) -To $testId -Relation 'validatedBy' }
}
}
foreach ($impact in @($visualImpact.impacts | Where-Object { $_.detectedVisualReferences -gt 0 })) {
$metric = @($catalog.metrics | Where-Object { $_.name -eq $impact.measure } | Select-Object -First 1)
foreach ($visual in @($impact.affectedVisuals)) {
$visualId = 'visual:' + (($visual.source + ':' + $visual.visual) -replace '[^A-Za-z0-9_-]', '-')
Add-Node -List $nodes -Id $visualId -Type 'Visual' -Label $visual.visual -Status $visual.visualType -Source $visual.source
if ($metric) { Add-Edge -List $edges -From ('metric:' + $metric.id) -To $visualId -Relation 'usedBy' }
}
}
$reviewArtifacts = @()
if ($ReviewDirectory -and (Test-Path -LiteralPath $ReviewDirectory)) {
$reviewArtifacts = @(Get-ChildItem -LiteralPath $ReviewDirectory -File -Filter '*.json' -ErrorAction SilentlyContinue | Sort-Object Name)
foreach ($artifact in $reviewArtifacts) {
$doc = Read-JsonArtifact -FilePath $artifact.FullName
$schema = if ($doc -and $doc.schema) { [string]$doc.schema } else { 'json' }
Add-Node -List $nodes -Id ('artifact:' + $artifact.BaseName) -Type 'ReviewArtifact' -Label $artifact.Name -Status $schema -Source $artifact.FullName
Add-Edge -List $edges -From 'model' -To ('artifact:' + $artifact.BaseName) -Relation 'reviewedBy'
}
}
$weakMetrics = @($trust.metrics | Where-Object { $_.trustScore -lt 70 })
$result = [pscustomobject]@{
schema = 'codex.powerbi.evidenceGraph.v1'
root = $root
generated = (Get-Date).ToString('s')
nodeCount = $nodes.Count
edgeCount = $edges.Count
reviewArtifactCount = @($reviewArtifacts).Count
evidenceStrength = if ($semantic.failedCount -eq 0 -and $semantic.pendingCount -eq 0 -and $weakMetrics.Count -eq 0) { 'High' } elseif ($semantic.testCount -gt 0 -or $trust.metricCount -gt 0) { 'Medium' } else { 'Low' }
weakestMetrics = @($weakMetrics | Sort-Object trustScore, name | Select-Object -First 10)
nodes = @($nodes.ToArray())
edges = @($edges.ToArray())
}
if ($Json) {
$text = $result | ConvertTo-Json -Depth 10
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $text -Encoding UTF8 }
$text
return
}
$lines = @(
'# Power BI Evidence Graph',
'',
('Evidence strength: **{0}**' -f $result.evidenceStrength),
('Nodes: {0}' -f $result.nodeCount),
('Edges: {0}' -f $result.edgeCount),
'',
'## Weakest Metrics'
) + @($result.weakestMetrics | ForEach-Object { '- `{0}`: trust {1}, band {2}' -f $_.name, $_.trustScore, $_.trustBand })
$content = ($lines -join [Environment]::NewLine) + [Environment]::NewLine
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $content -Encoding UTF8 }
$content
SHA-256: c53c0d0c94eac3b1244de50b8102444de792261839817f6fdfef4e102e86b60f