VBAF.Center.CrisisTree.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS VBAF-Center Phase 13 — Crisis Response Tree .DESCRIPTION When signals hit critical levels VBAF-Center activates a structured crisis response — walking the dispatcher through every possible recovery step. Like a defibrillator — it takes over when the human freezes. Human gut feeling stays in the background — the tree just makes sure nothing is forgotten in the heat of the moment. Domains: Logistics — vehicle breakdown, late delivery, driver missing IT — server down, disk full, CPU critical Healthcare — bed shortage, staff missing, equipment failure Manufacturing — machine breakdown, quality alert, supply delay Functions: Start-VBAFCenterCrisis — activate crisis response wizard Get-VBAFCenterCrisisTree — show all crisis trees New-VBAFCenterCrisisTree — add custom crisis tree Get-VBAFCenterCrisisHistory — show past crisis responses #> # ============================================================ # BUILT-IN CRISIS TREES — domain-based defaults # ============================================================ $script:CrisisTrees = @{ # ── LOGISTICS ──────────────────────────────────────────── "LOGISTICS-VEHICLE-BREAKDOWN" = @{ Domain = "Logistics" Trigger = "Empty Driving above 45% or Fleet Availability below 75%" Title = "Vehicle Breakdown Response" Steps = @( @{ Step=1; Question="Is a backup vehicle available in the depot?"; Yes="Deploy backup vehicle immediately — assign to affected route"; No="Proceed to Step 2" } @{ Step=2; Question="Can the load be split between remaining trucks?"; Yes="Redistribute load — prioritise time-sensitive deliveries first"; No="Proceed to Step 3" } @{ Step=3; Question="Can delivery time be renegotiated with the customer?"; Yes="Call customer now — explain situation, agree new ETA"; No="Proceed to Step 4" } @{ Step=4; Question="Is an external subcontractor available?"; Yes="Contact subcontractor — confirm availability and cost"; No="ESCALATE — Call operations manager immediately" } ) } "LOGISTICS-LATE-DELIVERY" = @{ Domain = "Logistics" Trigger = "On-Time Delivery below 65% or ETA Accuracy below 60%" Title = "Late Delivery Response" Steps = @( @{ Step=1; Question="Is the delay due to traffic or weather?"; Yes="Reroute to fastest available alternative — check Google Maps"; No="Proceed to Step 2" } @{ Step=2; Question="Is the delay more than 60 minutes?"; Yes="Call customer proactively — do not wait for them to call you"; No="Monitor — update ETA every 15 minutes" } @{ Step=3; Question="Is this a high-priority customer?"; Yes="Reassign nearest available truck to take over delivery"; No="Log delay — update delivery status in TMS" } ) } "LOGISTICS-HIGH-COST" = @{ Domain = "Logistics" Trigger = "Cost Per Trip above 2500 DKK" Title = "Cost Spike Response" Steps = @( @{ Step=1; Question="Is the cost spike due to fuel price?"; Yes="Check if routes can be shortened — combine deliveries where possible"; No="Proceed to Step 2" } @{ Step=2; Question="Are multiple trucks running near-empty?"; Yes="Consolidate loads — cancel unnecessary runs"; No="Proceed to Step 3" } @{ Step=3; Question="Is overtime driving the cost up?"; Yes="Review driver schedules — reassign to avoid overtime rates"; No="Log for weekly cost review — no immediate action needed" } ) } "LOGISTICS-DRIVER-MISSING" = @{ Domain = "Logistics" Trigger = "Fleet Availability below 80% due to driver absence" Title = "Driver Missing Response" Steps = @( @{ Step=1; Question="Can another driver cover the route today?"; Yes="Reassign immediately — update TMS with new driver"; No="Proceed to Step 2" } @{ Step=2; Question="Is a temp driver available from agency?"; Yes="Contact agency — confirm availability and ETA"; No="Proceed to Step 3" } @{ Step=3; Question="Can the deliveries be postponed to tomorrow?"; Yes="Contact customers — rebook deliveries"; No="ESCALATE — Call operations manager immediately" } ) } # ── IT INFRASTRUCTURE ──────────────────────────────────── "IT-SERVER-DOWN" = @{ Domain = "IT" Trigger = "Fleet Availability below 70% or CPU Load above 90%" Title = "Server Down Response" Steps = @( @{ Step=1; Question="Is the server responding to ping?"; Yes="Service likely crashed — attempt service restart"; No="Proceed to Step 2" } @{ Step=2; Question="Is a backup server available?"; Yes="Failover to backup server — notify users of temporary switch"; No="Proceed to Step 3" } @{ Step=3; Question="Can users work offline temporarily?"; Yes="Notify all users — estimated recovery time?"; No="ESCALATE — Call IT vendor immediately" } @{ Step=4; Question="Is this affecting business-critical systems?"; Yes="Declare incident — notify management and all affected departments"; No="Continue recovery — update status every 15 minutes" } ) } "IT-DISK-FULL" = @{ Domain = "IT" Trigger = "Disk Space Free below 20%" Title = "Disk Space Critical Response" Steps = @( @{ Step=1; Question="Are there old log files or temp files that can be deleted?"; Yes="Run disk cleanup — delete logs older than 30 days"; No="Proceed to Step 2" } @{ Step=2; Question="Are there old backups taking up space?"; Yes="Move old backups to archive storage or external drive"; No="Proceed to Step 3" } @{ Step=3; Question="Can additional storage be added quickly?"; Yes="Expand disk — contact IT vendor for emergency storage"; No="ESCALATE — Risk of system crash within hours" } ) } # ── MANUFACTURING ──────────────────────────────────────── "MFG-MACHINE-BREAKDOWN" = @{ Domain = "Manufacturing" Trigger = "Fleet Availability below 70% or Cost above threshold" Title = "Machine Breakdown Response" Steps = @( @{ Step=1; Question="Is a backup machine or line available?"; Yes="Switch production to backup line immediately"; No="Proceed to Step 2" } @{ Step=2; Question="Can production be rescheduled to another shift?"; Yes="Reschedule — notify shift manager and production planner"; No="Proceed to Step 3" } @{ Step=3; Question="Is the repair estimated under 2 hours?"; Yes="Hold production — begin repair immediately"; No="ESCALATE — Notify customers of potential delivery delays" } ) } # ── HEALTHCARE ─────────────────────────────────────────── "HEALTH-BED-SHORTAGE" = @{ Domain = "Healthcare" Trigger = "Capacity Utilisation above 95%" Title = "Bed Shortage Response" Steps = @( @{ Step=1; Question="Are any patients ready for discharge today?"; Yes="Expedite discharge process — free beds immediately"; No="Proceed to Step 2" } @{ Step=2; Question="Can any patients be transferred to another ward?"; Yes="Coordinate transfer with receiving ward"; No="Proceed to Step 3" } @{ Step=3; Question="Can elective procedures be postponed?"; Yes="Postpone non-urgent admissions — notify patients"; No="ESCALATE — Activate hospital overflow protocol" } ) } } # Customer-specific trees stored separately $script:CustomerCrisisTrees = @{} # ============================================================ # NEW-VBAFCENTERCRISISTREEE — add customer-specific tree # ============================================================ function New-VBAFCenterCrisisTree { <# .SYNOPSIS Add a customer-specific crisis tree. Customer trees take priority over domain defaults. .EXAMPLE New-VBAFCenterCrisisTree -CustomerID "TruckCompanyDK" ` -CrisisName "Tom kørsel kritisk" ` -Trigger "Empty Driving above 40%" ` -Step1 "Stop alle ikke-planlagte ture" ` -Step2 "Ring til alle chauffører" ` -Step3 "Tildel ledig lastbil til ventende levering" ` -Step4 "Opdater dispatcher" ` -Step5 "Log hændelsen i TMS" #> param( [Parameter(Mandatory)] [string] $CustomerID, [Parameter(Mandatory)] [string] $CrisisName, [Parameter(Mandatory)] [string] $Trigger, [Parameter(Mandatory)] [string] $Step1, [string] $Step2 = "", [string] $Step3 = "", [string] $Step4 = "", [string] $Step5 = "", [string] $ContactName = "", [string] $ContactPhone = "" ) if (-not $script:CustomerCrisisTrees.ContainsKey($CustomerID)) { $script:CustomerCrisisTrees[$CustomerID] = @() } $steps = @() $stepNum = 1 foreach ($s in @($Step1, $Step2, $Step3, $Step4, $Step5)) { if ($s -ne "") { $steps += @{ Step=$stepNum; Action=$s } $stepNum++ } } $tree = @{ CrisisName = $CrisisName Trigger = $Trigger Steps = $steps ContactName = $ContactName ContactPhone = $ContactPhone } $script:CustomerCrisisTrees[$CustomerID] += $tree # Persist to disk $crisisPath = Join-Path $env:USERPROFILE "VBAFCenter\crisisconfig" if (-not (Test-Path $crisisPath)) { New-Item -ItemType Directory -Path $crisisPath -Force | Out-Null } $script:CustomerCrisisTrees[$CustomerID] | ConvertTo-Json -Depth 5 | Set-Content "$crisisPath\$CustomerID-crisis.json" -Encoding UTF8 Write-Host (" Crisis tree added: {0}" -f $CrisisName) -ForegroundColor Green Write-Host (" Trigger : {0}" -f $Trigger) -ForegroundColor DarkGray Write-Host (" Steps : {0}" -f $steps.Count) -ForegroundColor DarkGray } # ============================================================ # LOAD CUSTOMER TREES FROM DISK (internal) # ============================================================ function Load-VBAFCenterCustomerCrisisTrees { $crisisPath = Join-Path $env:USERPROFILE "VBAFCenter\crisisconfig" if (-not (Test-Path $crisisPath)) { return } Get-ChildItem $crisisPath -Filter "*-crisis.json" | ForEach-Object { $customerID = $_.Name -replace "-crisis\.json", "" try { $trees = Get-Content $_.FullName -Raw | ConvertFrom-Json $script:CustomerCrisisTrees[$customerID] = @($trees) } catch {} } } # ============================================================ # START-VBAFCENTERCROSS # ============================================================ function Start-VBAFCenterCrisis { param( [Parameter(Mandatory)] [string] $CustomerID, [string] $CrisisType = "" ) # Load customer profile $profilePath = Join-Path $env:USERPROFILE "VBAFCenter\customers\$CustomerID.json" if (-not (Test-Path $profilePath)) { Write-Host "Customer not found: $CustomerID" -ForegroundColor Red return } $profile = Get-Content $profilePath -Raw | ConvertFrom-Json # Load customer-specific trees from disk Load-VBAFCenterCustomerCrisisTrees Write-Host "" Write-Host " +--------------------------------------------------+" -ForegroundColor Red Write-Host " | VBAF-Center CRISIS RESPONSE ACTIVATED |" -ForegroundColor Red Write-Host " | Follow the steps. Do not skip any. |" -ForegroundColor Red Write-Host " +--------------------------------------------------+" -ForegroundColor Red Write-Host (" | Customer : {0,-39}|" -f $profile.CompanyName) -ForegroundColor White Write-Host (" | Time : {0,-39}|" -f (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) -ForegroundColor White Write-Host " +--------------------------------------------------+" -ForegroundColor Red Write-Host "" # ── CHECK FOR CUSTOMER-SPECIFIC TREES FIRST ────────────── $hasCustomerTrees = $script:CustomerCrisisTrees.ContainsKey($CustomerID) -and $script:CustomerCrisisTrees[$CustomerID].Count -gt 0 if ($hasCustomerTrees) { # Use customer-specific trees $customerTrees = $script:CustomerCrisisTrees[$CustomerID] Write-Host " Select crisis type:" -ForegroundColor Yellow Write-Host "" $i = 1 $menu = @{} foreach ($tree in $customerTrees) { Write-Host (" {0}. {1}" -f $i, $tree.CrisisName) -ForegroundColor White Write-Host (" Trigger: {0}" -f $tree.Trigger) -ForegroundColor DarkGray $menu[$i.ToString()] = $tree $i++ } Write-Host "" $choice = Read-Host " Enter number" $selectedTree = $menu[$choice] if (-not $selectedTree) { Write-Host " Invalid selection." -ForegroundColor Red return } Write-Host "" Write-Host (" CRISIS: {0}" -f $selectedTree.CrisisName) -ForegroundColor Red Write-Host (" Trigger: {0}" -f $selectedTree.Trigger) -ForegroundColor Yellow Write-Host "" $startTime = Get-Date $responses = @() foreach ($step in $selectedTree.Steps) { Write-Host (" --- Step {0} ---" -f $step.Step) -ForegroundColor Cyan Write-Host (" {0}" -f $step.Action) -ForegroundColor White Write-Host "" $done = Read-Host " Done? (Y/N)" $responses += @{ Step=$step.Step; Action=$step.Action; Done=$done.ToUpper() } Write-Host "" } if ($selectedTree.ContactName -and $selectedTree.ContactName -ne "") { Write-Host (" Contact: {0} — {1}" -f $selectedTree.ContactName, $selectedTree.ContactPhone) -ForegroundColor Yellow Write-Host "" } $endTime = Get-Date $duration = [Math]::Round(($endTime - $startTime).TotalMinutes, 1) $resolution = "Resolved" # Save crisis log $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\crisis" if (-not (Test-Path $historyPath)) { New-Item -ItemType Directory -Path $historyPath -Force | Out-Null } $logFile = Join-Path $historyPath "$CustomerID-crisis-$(Get-Date -Format 'yyyyMMdd_HHmmss').json" @{ CustomerID = $CustomerID CrisisType = $selectedTree.CrisisName Title = $selectedTree.CrisisName StartTime = $startTime.ToString("yyyy-MM-dd HH:mm:ss") EndTime = $endTime.ToString("yyyy-MM-dd HH:mm:ss") Duration = $duration Resolution = $resolution Responses = $responses } | ConvertTo-Json -Depth 5 | Set-Content $logFile -Encoding UTF8 Write-Host " +--------------------------------------------------+" -ForegroundColor Green Write-Host (" | Crisis Resolution: {0,-31}|" -f $resolution) -ForegroundColor Green Write-Host (" | Duration : {0} minutes{1,-30}|" -f $duration, "") -ForegroundColor White Write-Host " +--------------------------------------------------+" -ForegroundColor Green Write-Host "" return } # ── FALLBACK TO DOMAIN-BASED DEFAULT TREES ─────────────── $domain = $profile.BusinessType.ToLower() # Fix: filter by Domain field only — no operator precedence issues $relevant = @($script:CrisisTrees.Keys | Where-Object { $script:CrisisTrees[$_].Domain.ToLower() -eq $domain }) if ($relevant.Count -eq 0) { Write-Host (" No crisis trees found for domain: {0}" -f $profile.BusinessType) -ForegroundColor Yellow Write-Host " Use New-VBAFCenterCrisisTree to add customer-specific trees." -ForegroundColor DarkGray return } Write-Host " Select crisis type:" -ForegroundColor Yellow Write-Host "" $i = 1 $menu = @{} foreach ($key in ($relevant | Sort-Object)) { $tree = $script:CrisisTrees[$key] Write-Host (" {0}. {1}" -f $i, $tree.Title) -ForegroundColor White Write-Host (" Trigger: {0}" -f $tree.Trigger) -ForegroundColor DarkGray $menu[$i.ToString()] = $key $i++ } Write-Host "" $choice = Read-Host " Enter number" $selectedKey = $menu[$choice] if (-not $selectedKey) { Write-Host " Invalid selection." -ForegroundColor Red return } $tree = $script:CrisisTrees[$selectedKey] $startTime = Get-Date $responses = @() $allResolved = $true Write-Host "" Write-Host (" CRISIS: {0}" -f $tree.Title) -ForegroundColor Red Write-Host (" Trigger: {0}" -f $tree.Trigger) -ForegroundColor Yellow Write-Host "" foreach ($step in $tree.Steps) { Write-Host (" --- Step {0} ---" -f $step.Step) -ForegroundColor Cyan Write-Host (" {0}" -f $step.Question) -ForegroundColor White Write-Host "" Write-Host " Y = Yes N = No" -ForegroundColor DarkGray Write-Host "" $answer = Read-Host " Your answer (Y/N)" if ($answer.ToUpper() -eq "Y") { Write-Host "" Write-Host (" ACTION: {0}" -f $step.Yes) -ForegroundColor Green $responses += @{ Step=$step.Step; Answer="Yes"; Action=$step.Yes } Write-Host "" $confirm = Read-Host " Action taken? Mark as done (Y/N)" if ($confirm.ToUpper() -eq "Y") { Write-Host " Step $($step.Step) complete." -ForegroundColor Green break } } else { Write-Host "" Write-Host (" CONTINUE: {0}" -f $step.No) -ForegroundColor Yellow $responses += @{ Step=$step.Step; Answer="No"; Action=$step.No } if ($step.No -like "ESCALATE*") { $allResolved = $false } } Write-Host "" } $endTime = Get-Date $duration = [Math]::Round(($endTime - $startTime).TotalMinutes, 1) $resolution = if ($allResolved) { "Resolved" } else { "Escalated" } $resColor = if ($allResolved) { "Green" } else { "Red" } $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\crisis" if (-not (Test-Path $historyPath)) { New-Item -ItemType Directory -Path $historyPath -Force | Out-Null } $logFile = Join-Path $historyPath "$CustomerID-crisis-$(Get-Date -Format 'yyyyMMdd_HHmmss').json" @{ CustomerID = $CustomerID CrisisType = $selectedKey Title = $tree.Title StartTime = $startTime.ToString("yyyy-MM-dd HH:mm:ss") EndTime = $endTime.ToString("yyyy-MM-dd HH:mm:ss") Duration = $duration Resolution = $resolution Responses = $responses } | ConvertTo-Json -Depth 5 | Set-Content $logFile -Encoding UTF8 Write-Host "" Write-Host " +--------------------------------------------------+" -ForegroundColor $resColor Write-Host (" | Crisis Resolution: {0,-31}|" -f $resolution) -ForegroundColor $resColor Write-Host (" | Duration : {0} minutes{1,-30}|" -f $duration, "") -ForegroundColor White Write-Host " +--------------------------------------------------+" -ForegroundColor $resColor Write-Host "" } # ============================================================ # GET-VBAFCENTERCRISISTREEE # ============================================================ function Get-VBAFCenterCrisisTree { param([string] $CustomerID = "") Load-VBAFCenterCustomerCrisisTrees Write-Host "" if ($CustomerID -ne "" -and $script:CustomerCrisisTrees.ContainsKey($CustomerID)) { Write-Host (" Customer Crisis Trees: {0}" -f $CustomerID) -ForegroundColor Cyan Write-Host "" Write-Host (" {0,-35} {1}" -f "Crisis Name", "Trigger") -ForegroundColor Yellow Write-Host (" {0}" -f ("-" * 70)) -ForegroundColor DarkGray foreach ($tree in $script:CustomerCrisisTrees[$CustomerID]) { Write-Host (" {0,-35} {1}" -f $tree.CrisisName, $tree.Trigger) -ForegroundColor White } } else { Write-Host " VBAF-Center Built-in Crisis Trees" -ForegroundColor Cyan Write-Host "" Write-Host (" {0,-35} {1,-15} {2}" -f "Title", "Domain", "Steps") -ForegroundColor Yellow Write-Host (" {0}" -f ("-" * 70)) -ForegroundColor DarkGray foreach ($key in ($script:CrisisTrees.Keys | Sort-Object)) { $tree = $script:CrisisTrees[$key] Write-Host (" {0,-35} {1,-15} {2}" -f $tree.Title, $tree.Domain, $tree.Steps.Count) -ForegroundColor White } } Write-Host "" } # ============================================================ # GET-VBAFCENTERCRISISHISTORY # ============================================================ function Get-VBAFCenterCrisisHistory { param( [Parameter(Mandatory)] [string] $CustomerID, [int] $Last = 10 ) $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\crisis" if (-not (Test-Path $historyPath)) { Write-Host "No crisis history found." -ForegroundColor Yellow return } $logs = Get-ChildItem $historyPath -Filter "$CustomerID-crisis-*.json" | Sort-Object LastWriteTime -Descending | Select-Object -First $Last if ($logs.Count -eq 0) { Write-Host "No crisis history for $CustomerID." -ForegroundColor Yellow return } Write-Host "" Write-Host (" Crisis History: {0} (last {1})" -f $CustomerID, $Last) -ForegroundColor Cyan Write-Host (" {0,-22} {1,-30} {2,-12} {3}" -f "Time", "Crisis", "Duration", "Resolution") -ForegroundColor Yellow Write-Host (" {0}" -f ("-" * 80)) -ForegroundColor DarkGray foreach ($log in $logs) { $h = Get-Content $log.FullName -Raw | ConvertFrom-Json $color = if ($h.Resolution -eq "Resolved") { "Green" } else { "Red" } Write-Host (" {0,-22} {1,-30} {2,-12} {3}" -f ` $h.StartTime, $h.Title, "$($h.Duration) min", $h.Resolution) -ForegroundColor $color } Write-Host "" } # ============================================================ # LOAD CUSTOMER TREES ON STARTUP # ============================================================ Load-VBAFCenterCustomerCrisisTrees # ============================================================ # LOAD MESSAGE # ============================================================ Write-Host "" Write-Host " +--------------------------------------------------+" -ForegroundColor Red Write-Host " | VBAF-Center Phase 13 - Crisis Response Tree |" -ForegroundColor Red Write-Host " | Step-by-step recovery when signals go critical |" -ForegroundColor Red Write-Host " +--------------------------------------------------+" -ForegroundColor Red Write-Host "" Write-Host " Start-VBAFCenterCrisis — activate crisis wizard" -ForegroundColor White Write-Host " Get-VBAFCenterCrisisTree — show all crisis trees" -ForegroundColor White Write-Host " New-VBAFCenterCrisisTree — add customer-specific tree" -ForegroundColor White Write-Host " Get-VBAFCenterCrisisHistory — show past crisis responses" -ForegroundColor White Write-Host "" Write-Host " Quick start:" -ForegroundColor Yellow Write-Host " Start-VBAFCenterCrisis -CustomerID 'TruckCompanyDK'" -ForegroundColor Green Write-Host " Get-VBAFCenterCrisisTree -CustomerID 'TruckCompanyDK'" -ForegroundColor Green Write-Host "" |