← Files Power BI DesktopARCHIVED FILE

scripts/New-PowerBIGuidedFixPlan.ps1

5.47 KB · Oct 2, 2026 · 00:32 UTC

↓ Download file

param(
    [string]$Path = ".",
    [string]$OutputPath,
    [switch]$Json
)

$ErrorActionPreference = 'Stop'

$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$scan = & (Join-Path $scriptRoot 'Invoke-PowerBIInsightScan.ps1') -Path $Path -Json | ConvertFrom-Json
$catalog = & (Join-Path $scriptRoot 'New-PowerBIMetricCatalog.ps1') -Path $Path -Json | ConvertFrom-Json
$impact = & (Join-Path $scriptRoot 'New-PowerBIMeasureLineageImpact.ps1') -Path $Path -Json | ConvertFrom-Json
$visualIntent = & (Join-Path $scriptRoot 'New-PowerBIVisualIntentAnalyzer.ps1') -Path $Path -Json | ConvertFrom-Json

$fixes = New-Object System.Collections.Generic.List[object]
foreach ($finding in @($scan.findings)) {
    $priority = if ($finding.severity -eq 'High') { 'P0' } elseif ($finding.severity -eq 'Medium') { 'P1' } else { 'P2' }
    $problemText = if ($finding.message) { $finding.message } elseif ($finding.detail) { $finding.detail } else { $finding.title }
    $fixes.Add([pscustomobject]@{
        priority = $priority
        theme = $finding.category
        source = $finding.source
        problem = $problemText
        guidedStep = 'Inspect the cited file or measure, prepare a reviewed change plan, then rerun scan and measure tests.'
        nextScript = 'Invoke-PowerBIInsightScan.ps1'
        validation = 'Run Invoke-PowerBIInsightScan.ps1 and New-PowerBIMeasureTestPlan.ps1 for the affected model.'
        releaseGate = if ($priority -eq 'P0') { 'Blocks release until closed or explicitly waived.' } else { 'Document before release if not closed.' }
        requiresApplyConfirmation = $true
        status = 'Open'
    })
}
foreach ($metric in @($catalog.metrics | Where-Object { @($_.risks).Count -gt 0 })) {
    $impactItem = @($impact.measures | Where-Object { $_.name -eq $metric.name } | Select-Object -First 1)
    $priority = if ($impactItem.downstreamCount -gt 0 -or $metric.riskLevel -eq 'high') { 'P0' } else { 'P1' }
    $fixes.Add([pscustomobject]@{
        priority = $priority
        theme = 'DAX Refactoring'
        source = $metric.name
        problem = (@($metric.risks) -join '; ')
        guidedStep = 'Draft a narrower or deterministic measure rewrite, preserving accepted business totals. Do not apply until reviewed.'
        nextScript = 'New-PowerBIDaxFixSimulation.ps1'
        validation = ('Run generated tests for `{0}` and validate downstream count {1}.' -f $metric.name, $impactItem.downstreamCount)
        releaseGate = if ($priority -eq 'P0') { 'Blocks release until downstream measures and business totals are validated.' } else { 'Requires validation evidence before release.' }
        requiresApplyConfirmation = $true
        status = 'Open'
    })
}
foreach ($finding in @($visualIntent.findings | Where-Object { $_.severity -in @('High', 'Medium', 'NeedsInput') })) {
    $priority = if ($finding.severity -eq 'High') { 'P0' } elseif ($finding.severity -eq 'Medium') { 'P1' } else { 'P2' }
    $fixes.Add([pscustomobject]@{
        priority = $priority
        theme = $finding.category
        source = $finding.source
        problem = $finding.title
        guidedStep = $finding.detail
        nextScript = 'New-PowerBIVisualIntentAnalyzer.ps1'
        validation = 'Rerun visual intent analysis and screenshot UX review when report screenshots are available.'
        releaseGate = 'Review unresolved report/visual findings before release candidate sign-off.'
        requiresApplyConfirmation = $true
        status = 'Open'
    })
}

$ordered = @($fixes.ToArray() | Sort-Object @{ Expression = { @{ P0 = 0; P1 = 1; P2 = 2 }[$_.priority] } }, Source)
$result = [pscustomobject]@{
    schema = 'codex.powerbi.guidedFixPlan.v1'
    root = $scan.root
    generated = (Get-Date).ToString('s')
    mode = 'PlanOnly'
    mutatingFixesApplied = $false
    fixCount = $ordered.Count
    releaseGates = @(
        [pscustomobject]@{ name = 'P0 fixes'; status = $(if (@($ordered | Where-Object priority -eq 'P0').Count -eq 0) { 'Pass' } else { 'Fail' }); detail = 'No open P0 fixes before publish.' },
        [pscustomobject]@{ name = 'Visual intent review'; status = $(if ($visualIntent.reportMetadataStatus -eq 'Available') { 'Pass' } else { 'Warn' }); detail = 'Report metadata should be available for visual intelligence.' },
        [pscustomobject]@{ name = 'Apply confirmation'; status = 'Pass'; detail = 'This plan does not mutate files. Apply-capable scripts require explicit -Apply or equivalent confirmation.' }
    )
    fixes = $ordered
}

if ($Json) {
    $text = $result | ConvertTo-Json -Depth 8
    if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $text -Encoding UTF8 }
    $text
    return
}

$md = New-Object System.Collections.Generic.List[string]
$md.Add('# Power BI Guided Fix Plan')
$md.Add('')
$md.Add(('Fixes: {0}' -f $result.fixCount))
$md.Add('')
foreach ($fix in $result.fixes) {
    $md.Add(('## [{0}] {1}' -f $fix.priority, $fix.source))
    $md.Add(('- Theme: {0}' -f $fix.theme))
    $md.Add(('- Problem: {0}' -f $fix.problem))
    $md.Add(('- Guided step: {0}' -f $fix.guidedStep))
    $md.Add(('- Next script: {0}' -f $fix.nextScript))
    $md.Add(('- Validation: {0}' -f $fix.validation))
    $md.Add(('- Release gate: {0}' -f $fix.releaseGate))
    $md.Add(('- Status: {0}' -f $fix.status))
    $md.Add('')
}
$content = ($md -join [Environment]::NewLine) + [Environment]::NewLine
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $content -Encoding UTF8 }
$content

SHA-256: 127f567fad3e9640051449d5425803412a1f6fbab7ca0ec62ec6d988eef5314b