Private/Update-RunbookLearning.ps1
|
function Update-RunbookLearning { <# .SYNOPSIS Records the outcome of a runbook step for the learning system. .DESCRIPTION Stores success/failure data for each runbook step in the learnings.json file. Tracks success rates per step, common failure patterns, and recommended alternatives. This data feeds back into future runbook executions to improve decision-making. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$RunbookName, [Parameter(Mandatory)] [string]$StepId, [Parameter(Mandatory)] [string]$Action, [Parameter(Mandatory)] [bool]$Succeeded, [Parameter()] [string]$ComputerName, [Parameter()] [hashtable]$Context = @{}, [Parameter()] [string]$LearningsPath ) if (-not $LearningsPath) { $LearningsPath = Join-Path $env:USERPROFILE '.runbookengine\learnings.json' } # Ensure parent directory exists $parentDir = Split-Path $LearningsPath -Parent if (-not (Test-Path $parentDir)) { New-Item -Path $parentDir -ItemType Directory -Force | Out-Null } # Load existing learnings $learnings = @() if (Test-Path $LearningsPath) { try { $raw = Get-Content -Path $LearningsPath -Raw -ErrorAction Stop if ($raw -and $raw.Trim().Length -gt 2) { $learnings = @($raw | ConvertFrom-Json -ErrorAction Stop) } } catch { Write-Verbose "Failed to parse learnings file, starting fresh: $_" $learnings = @() } } # Find existing entry for this runbook+step combination $existingIdx = -1 for ($i = 0; $i -lt $learnings.Count; $i++) { if ($learnings[$i].RunbookName -eq $RunbookName -and $learnings[$i].StepId -eq $StepId) { $existingIdx = $i break } } $timestamp = (Get-Date).ToString('o') if ($existingIdx -ge 0) { # Update existing entry $entry = $learnings[$existingIdx] $totalRuns = if ($entry.TotalRuns) { [int]$entry.TotalRuns + 1 } else { 1 } $successes = if ($entry.Successes) { [int]$entry.Successes } else { 0 } $failures = if ($entry.Failures) { [int]$entry.Failures } else { 0 } if ($Succeeded) { $successes++ } else { $failures++ } $successRate = if ($totalRuns -gt 0) { [math]::Round(($successes / $totalRuns) * 100, 1) } else { 0 } # Build updated recent executions list $recentExecutions = @() if ($entry.RecentExecutions) { $recentExecutions = @($entry.RecentExecutions) } $newExecution = [PSCustomObject]@{ Timestamp = $timestamp ComputerName = $ComputerName Succeeded = $Succeeded Context = $Context } $recentExecutions = @($recentExecutions) + @($newExecution) # Keep only last 50 executions if ($recentExecutions.Count -gt 50) { $recentExecutions = $recentExecutions | Select-Object -Last 50 } # Detect common failure patterns $failurePatterns = @() if ($entry.FailurePatterns) { $failurePatterns = @($entry.FailurePatterns) } if (-not $Succeeded -and $Context.ContainsKey('Error')) { $errorMsg = $Context['Error'] $patternFound = $false for ($j = 0; $j -lt $failurePatterns.Count; $j++) { if ($failurePatterns[$j].Pattern -eq $errorMsg) { $failurePatterns[$j] = [PSCustomObject]@{ Pattern = $errorMsg Count = ([int]$failurePatterns[$j].Count + 1) LastSeen = $timestamp } $patternFound = $true break } } if (-not $patternFound) { $failurePatterns += [PSCustomObject]@{ Pattern = $errorMsg Count = 1 LastSeen = $timestamp } } } $updatedEntry = [PSCustomObject]@{ RunbookName = $RunbookName StepId = $StepId Action = $Action TotalRuns = $totalRuns Successes = $successes Failures = $failures SuccessRate = $successRate LastRunAt = $timestamp LastResult = if ($Succeeded) { 'Success' } else { 'Failure' } RecentExecutions = $recentExecutions FailurePatterns = $failurePatterns } $learnings[$existingIdx] = $updatedEntry } else { # Create new entry $newEntry = [PSCustomObject]@{ RunbookName = $RunbookName StepId = $StepId Action = $Action TotalRuns = 1 Successes = if ($Succeeded) { 1 } else { 0 } Failures = if ($Succeeded) { 0 } else { 1 } SuccessRate = if ($Succeeded) { 100.0 } else { 0.0 } LastRunAt = $timestamp LastResult = if ($Succeeded) { 'Success' } else { 'Failure' } RecentExecutions = @( [PSCustomObject]@{ Timestamp = $timestamp ComputerName = $ComputerName Succeeded = $Succeeded Context = $Context } ) FailurePatterns = @() } if (-not $Succeeded -and $Context.ContainsKey('Error')) { $newEntry.FailurePatterns = @( [PSCustomObject]@{ Pattern = $Context['Error'] Count = 1 LastSeen = $timestamp } ) } $learnings = @($learnings) + @($newEntry) } # Save back to file $learnings | ConvertTo-Json -Depth 10 | Set-Content -Path $LearningsPath -Encoding UTF8 # Return the updated/new entry if ($existingIdx -ge 0) { return $learnings[$existingIdx] } else { return $learnings[-1] } } |