VBAF.Center.ClaudeBrain.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS VBAF-Center Phase 19 — Claude Brain .DESCRIPTION Replaces the if-statement decision engine with real AI. Claude analyses signals, history, customer profile and action map and returns a full situational assessment in Danish. Option C — Full intelligence: Action number (0-3) Reason in plain Danish Specific dispatcher instruction Pattern recognition from history Functions: Invoke-VBAFCenterClaudeBrain — run full AI analysis Get-VBAFCenterClaudeBrainHistory — show AI decision history Set-VBAFCenterClaudeAPIKey — save API key Get-VBAFCenterClaudeAPIKey — verify API key is set #> $script:ClaudeConfigPath = Join-Path $env:USERPROFILE "VBAFCenter\claude" $script:ClaudeAPIURL = "https://api.anthropic.com/v1/messages" $script:ClaudeModel = "claude-sonnet-4-20250514" function Initialize-VBAFCenterClaudeStore { if (-not (Test-Path $script:ClaudeConfigPath)) { New-Item -ItemType Directory -Path $script:ClaudeConfigPath -Force | Out-Null } } # ============================================================ # SET-VBAFCENTERCLAUDEAPIKEY # ============================================================ function Set-VBAFCenterClaudeAPIKey { <# .SYNOPSIS Save your Anthropic API key securely to disk. Get your key from: https://console.anthropic.com .EXAMPLE Set-VBAFCenterClaudeAPIKey -APIKey "sk-ant-xxxx" #> param( [Parameter(Mandatory)] [string] $APIKey ) Initialize-VBAFCenterClaudeStore $configFile = Join-Path $script:ClaudeConfigPath "claude-config.json" @{ APIKey = $APIKey; SavedAt = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } | ConvertTo-Json | Set-Content $configFile -Encoding UTF8 Write-Host "" Write-Host "Claude API key saved!" -ForegroundColor Green Write-Host "Test with: Invoke-VBAFCenterClaudeBrain -CustomerID 'TruckCompanyDK'" -ForegroundColor DarkGray Write-Host "" } # ============================================================ # GET-VBAFCENTERCLAUDEAPIKEY (internal) # ============================================================ function Get-VBAFCenterClaudeAPIKey { Initialize-VBAFCenterClaudeStore $configFile = Join-Path $script:ClaudeConfigPath "claude-config.json" if (-not (Test-Path $configFile)) { Write-Host "No API key found." -ForegroundColor Red Write-Host "Run: Set-VBAFCenterClaudeAPIKey -APIKey 'sk-ant-xxxx'" -ForegroundColor Yellow return $null } $config = Get-Content $configFile -Raw | ConvertFrom-Json return $config.APIKey } # ============================================================ # BUILD CLAUDE PROMPT (internal) # ============================================================ function Build-VBAFCenterClaudePrompt { param( [string] $CustomerID, [object] $Profile, [object[]] $Signals, [object[]] $History, [object] $ActionMap, [double] $WeightedAvg, [object[]] $RedSignals, [object[]] $YellowSignals ) # Build signal description $signalText = "" foreach ($s in $Signals) { $status = $s.SignalColour if (-not $status) { $status = if ($s.Normalised -gt 0.75) { "RED" } elseif ($s.Normalised -gt 0.40) { "YELLOW" } else { "GREEN" } } $threshText = "" if ($s.GoodBelow -ge 0 -or $s.BadAbove -ge 0) { $threshText = " (god: under $($s.GoodBelow), kritisk: over $($s.BadAbove))" } $signalText += " - $($s.SignalName): $($s.RawValue) $threshText — $status`n" } # Build history description (last 5 runs) $historyText = "" if ($History -and $History.Count -gt 0) { $recent = $History | Select-Object -Last 5 foreach ($h in $recent) { $historyText += " - $($h.Timestamp): $($h.ActionName) (avg $($h.AvgSignal))" if ($h.OverrideApplied) { $historyText += " [OVERRIDE]" } $historyText += "`n" } } else { $historyText = " Ingen historik endnu.`n" } # Build action map description $actionText = "" if ($ActionMap) { $actionText = @" Action 0 (Monitor) : $($ActionMap.Action0Command) Action 1 (Reassign) : $($ActionMap.Action1Command) Action 2 (Reroute) : $($ActionMap.Action2Command) Action 3 (Escalate) : $($ActionMap.Action3Command) "@ } else { $actionText = " Standard: Monitor / Reassign / Reroute / Escalate`n" } $redCount = if ($RedSignals) { @($RedSignals).Count } else { 0 } $yellowCount = if ($YellowSignals) { @($YellowSignals).Count } else { 0 } return @" Du er en driftsassistent for $($Profile.CompanyName) — en $($Profile.BusinessType) virksomhed i Danmark. Dit job er at analysere de aktuelle driftssignaler og anbefale den rigtige handling til dispatcheren. Svar ALTID på dansk. Vær konkret og direkte. Ingen lange forklaringer. KUNDEPROFIL: Virksomhed : $($Profile.CompanyName) Branche : $($Profile.BusinessType) Problem : $($Profile.Problem) Agent : $($Profile.Agent) AKTUELLE SIGNALER (nu): $signalText SIGNAL OVERSIGT: Vægtet gennemsnit : $([Math]::Round($WeightedAvg, 4)) Røde signaler : $redCount Gule signaler : $yellowCount SENESTE HISTORIK (de 5 nyeste kørsler): $historyText KUNDENS HANDLINGSMULIGHEDER: $actionText DIN OPGAVE: Analyser situationen og returner KUN dette JSON-objekt — intet andet: { "Action": <0, 1, 2 eller 3>, "ActionName": "<Monitor, Reassign, Reroute eller Escalate>", "Reason": "<2-3 sætninger på dansk — hvad ser du i signalerne og hvorfor er det bekymrende eller OK>", "Instruction": "<1-2 konkrete sætninger til dispatcheren — præcis hvad skal de gøre NU>", "Pattern": "<1 sætning om mønster på tværs af historik — eller tom streng hvis ingen mønster>", "Confidence": "<Høj, Medium eller Lav>" } REGLER: - Action 0 (Monitor) : alt er OK — ingen handling nødvendig - Action 1 (Reassign) : noget kræver opmærksomhed — flyt en ressource - Action 2 (Reroute) : alvorlig situation — skift tilgang nu - Action 3 (Escalate) : krise — ring til et menneske med det samme - Hvis et rødt signal er til stede — minimum Action 2 - Hvis 2+ røde signaler — minimum Action 3 - Brug kundens egne handlingsord fra HANDLINGSMULIGHEDER ovenfor "@ } # ============================================================ # INVOKE-VBAFCENTERCLAUDEBRAIN # ============================================================ function Invoke-VBAFCenterClaudeBrain { <# .SYNOPSIS Full AI analysis of current signals using Claude. Replaces the if-statement with real intelligence. Returns action, reason, dispatcher instruction and pattern analysis. .EXAMPLE Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Verbose #> param( [Parameter(Mandatory)] [string] $CustomerID, [switch] $Verbose ) # Get API key $apiKey = Get-VBAFCenterClaudeAPIKey if (-not $apiKey) { return $null } Write-Host "" Write-Host ("Claude Brain: {0} — {1}" -f $CustomerID, (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) -ForegroundColor Cyan Write-Host " Gathering context..." -ForegroundColor DarkGray # -------------------------------------------------------- # Step 1 — 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 $null } $profile = Get-Content $profilePath -Raw | ConvertFrom-Json # -------------------------------------------------------- # Step 2 — Get live signals via Phase 3 # -------------------------------------------------------- $signalResult = $null $signals = @() $weightedAvg = 0.0 $redSignals = @() $yellowSignals = @() if (Get-Command Get-VBAFCenterAllSignals -ErrorAction SilentlyContinue) { $signalResult = Get-VBAFCenterAllSignals -CustomerID $CustomerID $signals = @($signalResult.Signals) $weightedAvg = if ($signalResult.WeightedAvg) { $signalResult.WeightedAvg } else { $signalResult.SimpleAvg } $redSignals = @($signalResult.RedSignals) $yellowSignals = @($signalResult.YellowSignals) } else { Write-Host " Phase 3 not loaded — load VBAF.Center.SignalAcquisition.ps1 first." -ForegroundColor Yellow return $null } if ($signals.Count -eq 0) { Write-Host " No signals configured for: $CustomerID" -ForegroundColor Yellow return $null } # -------------------------------------------------------- # Step 3 — Load run history # -------------------------------------------------------- $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history" $history = @() if (Test-Path $historyPath) { $historyFiles = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" | Sort-Object LastWriteTime -Descending | Select-Object -First 10 foreach ($f in $historyFiles) { try { $history += Get-Content $f.FullName -Raw | ConvertFrom-Json } catch {} } $history = @($history | Sort-Object Timestamp) } # -------------------------------------------------------- # Step 4 — Load action map # -------------------------------------------------------- $actionMap = $null $actionFile = Join-Path $env:USERPROFILE "VBAFCenter\actions\$CustomerID-actions.txt" if (Test-Path $actionFile) { $lines = Get-Content $actionFile $actionMap = [PSCustomObject]@{ Action0Command = ($lines | Where-Object { $_ -match "^0\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join "" Action1Command = ($lines | Where-Object { $_ -match "^1\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join "" Action2Command = ($lines | Where-Object { $_ -match "^2\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join "" Action3Command = ($lines | Where-Object { $_ -match "^3\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join "" } } # -------------------------------------------------------- # Step 5 — Build prompt and call Claude # -------------------------------------------------------- $prompt = Build-VBAFCenterClaudePrompt ` -CustomerID $CustomerID ` -Profile $profile ` -Signals $signals ` -History $history ` -ActionMap $actionMap ` -WeightedAvg $weightedAvg ` -RedSignals $redSignals ` -YellowSignals $yellowSignals if ($Verbose) { Write-Host "" Write-Host " Prompt sent to Claude:" -ForegroundColor DarkGray Write-Host $prompt -ForegroundColor DarkGray Write-Host "" } Write-Host " Calling Claude..." -ForegroundColor DarkGray $body = @{ model = $script:ClaudeModel max_tokens = 1000 messages = @( @{ role = "user"; content = $prompt } ) } | ConvertTo-Json -Depth 5 $headers = @{ "x-api-key" = $apiKey "anthropic-version" = "2023-06-01" "content-type" = "application/json" } $claudeResponse = $null try { $response = Invoke-RestMethod -Uri $script:ClaudeAPIURL -Method POST -Headers $headers -Body $body -ErrorAction Stop $rawText = $response.content[0].text $claudeResponse = $rawText | ConvertFrom-Json } catch { Write-Host (" Claude API call failed: {0}" -f $_.Exception.Message) -ForegroundColor Red Write-Host " Check your API key with: Get-VBAFCenterClaudeAPIKey" -ForegroundColor Yellow return $null } # -------------------------------------------------------- # Step 6 — Display result # -------------------------------------------------------- $action = [int]$claudeResponse.Action $actionName = $claudeResponse.ActionName $reason = $claudeResponse.Reason $instruction = $claudeResponse.Instruction $pattern = $claudeResponse.Pattern $confidence = $claudeResponse.Confidence $actionColors = @("Green","Yellow","DarkYellow","Red") $color = $actionColors[$action] Write-Host "" Write-Host (" Action : {0} — {1}" -f $action, $actionName) -ForegroundColor $color Write-Host (" Confidence : {0}" -f $confidence) -ForegroundColor White Write-Host "" Write-Host " Reason:" -ForegroundColor Yellow Write-Host (" {0}" -f $reason) -ForegroundColor White Write-Host "" Write-Host " Instruction to dispatcher:" -ForegroundColor Yellow Write-Host (" {0}" -f $instruction) -ForegroundColor $color if ($pattern -and $pattern -ne "") { Write-Host "" Write-Host " Pattern:" -ForegroundColor Cyan Write-Host (" {0}" -f $pattern) -ForegroundColor Cyan } Write-Host "" # -------------------------------------------------------- # Step 7 — Save result to history # -------------------------------------------------------- $result = [PSCustomObject]@{ CustomerID = $CustomerID Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff") Signals = @($signals | ForEach-Object { $_.Normalised }) AvgSignal = [Math]::Round($weightedAvg, 4) WeightedAvg = [Math]::Round($weightedAvg, 4) Action = $action ActionName = $actionName ActionCommand = $instruction ActionReason = $reason Pattern = $pattern Confidence = $confidence OverrideApplied = ($redSignals.Count -gt 0) RedSignalCount = $redSignals.Count YellowSignalCount = $yellowSignals.Count Source = "Claude" } $histFile = Join-Path $historyPath "$CustomerID-$(Get-Date -Format 'yyyyMMdd_HHmmss_fff').json" if (-not (Test-Path $historyPath)) { New-Item -ItemType Directory -Path $historyPath -Force | Out-Null } $result | ConvertTo-Json -Depth 5 | Set-Content $histFile -Encoding UTF8 # -------------------------------------------------------- # Step 8 — Crisis response if Action 3 # -------------------------------------------------------- if ($action -ge 3) { Write-Host "" Write-Host " [CRISIS] Claude recommends Escalate — activating crisis response!" -ForegroundColor Red Write-Host "" try { [Console]::Beep(800, 400) Start-Sleep -Milliseconds 100 [Console]::Beep(1000, 400) Start-Sleep -Milliseconds 100 [Console]::Beep(1500, 800) } catch {} try { Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $form = New-Object System.Windows.Forms.Form $form.Text = "VBAF CRISIS — Claude Brain" $form.Size = New-Object System.Drawing.Size(480, 280) $form.StartPosition = "CenterScreen" $form.TopMost = $true $form.BackColor = [System.Drawing.Color]::Red $label = New-Object System.Windows.Forms.Label $label.Text = ("KRISE DETEKTERET!`n`nKunde : {0}`nHandling: {1}`n`nKlauds vurdering:`n{2}`n`nInstruktion:`n{3}`n`nKlik OK for at bekræfte." -f ` $CustomerID, $actionName, $reason, $instruction) $label.ForeColor = [System.Drawing.Color]::White $label.Font = New-Object System.Drawing.Font("Arial", 9, [System.Drawing.FontStyle]::Regular) $label.Size = New-Object System.Drawing.Size(450, 200) $label.Location = New-Object System.Drawing.Point(10, 10) $button = New-Object System.Windows.Forms.Button $button.Text = "OK — Jeg håndterer det" $button.Size = New-Object System.Drawing.Size(200, 35) $button.Location = New-Object System.Drawing.Point(130, 220) $button.BackColor = [System.Drawing.Color]::White $button.ForeColor = [System.Drawing.Color]::Red $button.Font = New-Object System.Drawing.Font("Arial", 10, [System.Drawing.FontStyle]::Bold) $button.Add_Click({ $form.Close() }) $form.Controls.Add($label) $form.Controls.Add($button) $form.Add_Shown({ $form.Activate() }) $form.ShowDialog() | Out-Null } catch {} if (Get-Command Start-VBAFCenterCrisis -ErrorAction SilentlyContinue) { Start-VBAFCenterCrisis -CustomerID $CustomerID } } return $result } # ============================================================ # GET-VBAFCENTERCLAUDEBRAINHISTORY # ============================================================ function Get-VBAFCenterClaudeBrainHistory { <# .SYNOPSIS Show recent Claude Brain decisions for a customer. .EXAMPLE Get-VBAFCenterClaudeBrainHistory -CustomerID "TruckCompanyDK" #> param( [Parameter(Mandatory)] [string] $CustomerID, [int] $Last = 10 ) $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history" if (-not (Test-Path $historyPath)) { Write-Host "No history found." -ForegroundColor Yellow return } $files = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" | Sort-Object LastWriteTime -Descending | Select-Object -First $Last $claudeOnly = @() foreach ($f in $files) { try { $h = Get-Content $f.FullName -Raw | ConvertFrom-Json if ($h.Source -eq "Claude") { $claudeOnly += $h } } catch {} } if ($claudeOnly.Count -eq 0) { Write-Host "No Claude Brain decisions found yet." -ForegroundColor Yellow Write-Host "Run: Invoke-VBAFCenterClaudeBrain -CustomerID '$CustomerID'" -ForegroundColor DarkGray return } Write-Host "" Write-Host ("Claude Brain History: {0} (last {1})" -f $CustomerID, $claudeOnly.Count) -ForegroundColor Cyan Write-Host (" {0,-23} {1,-4} {2,-10} {3,-8} {4}" -f "Timestamp","Act","Name","Conf","Reason") -ForegroundColor Yellow Write-Host (" {0}" -f ("-" * 85)) -ForegroundColor DarkGray foreach ($h in $claudeOnly) { $color = @("Green","Yellow","DarkYellow","Red")[[int]$h.Action] Write-Host (" {0,-23} {1,-4} {2,-10} {3,-8} {4}" -f ` $h.Timestamp, $h.Action, $h.ActionName, $h.Confidence, ($h.ActionReason -replace "`n"," " | ForEach-Object { if ($_.Length -gt 50) { $_.Substring(0,50) + "..." } else { $_ } })) -ForegroundColor $color } Write-Host "" } # ============================================================ # LOAD MESSAGE # ============================================================ Write-Host "" Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan Write-Host " | VBAF-Center Phase 19 — Claude Brain |" -ForegroundColor Cyan Write-Host " | Real AI replaces the if-statement |" -ForegroundColor Cyan Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan Write-Host "" Write-Host " Set-VBAFCenterClaudeAPIKey — save your Anthropic API key" -ForegroundColor White Write-Host " Invoke-VBAFCenterClaudeBrain — full AI analysis per customer" -ForegroundColor White Write-Host " Get-VBAFCenterClaudeBrainHistory — show recent AI decisions" -ForegroundColor White Write-Host "" $apiKey = Get-VBAFCenterClaudeAPIKey if ($apiKey) { Write-Host " API key: configured" -ForegroundColor Green } else { Write-Host " API key: NOT configured" -ForegroundColor Yellow Write-Host " Run: Set-VBAFCenterClaudeAPIKey -APIKey 'sk-ant-xxxx'" -ForegroundColor DarkGray } Write-Host "" <# SETUP — once only: Step 1 — Get your Anthropic API key: Go to https://console.anthropic.com — create an account if needed — copy your API key (starts with sk-ant-) Step 2 — Save the file from ISE: Save as C:\Users\henni\OneDrive\WindowsPowerShell\VBAF-Center\VBAF.Center.ClaudeBrain.ps1 Step 3 — Save your API key: powershellcd "C:\Users\henni\OneDrive\WindowsPowerShell" . .\VBAF-Center\VBAF.Center.ClaudeBrain.ps1 Set-VBAFCenterClaudeAPIKey -APIKey "sk-ant-xxxx" DAILY USE — the two options: Option A — Manual run (testing and demos): powershellcd "C:\Users\henni\OneDrive\WindowsPowerShell" . .\VBAF-Center\VBAF.Center.LoadAll.ps1 . .\VBAF-Center\VBAF.Center.ClaudeBrain.ps1 Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" Option B — Automatic 24/7 via Scheduler: The Scheduler already calls Invoke-VBAFCenterRun every 10 minutes. You have two choices: Choice 1 — Replace the brain entirely: Edit VBAF.Center.Scheduler.ps1 — replace the Invoke-VBAFCenterRoute call with Invoke-VBAFCenterClaudeBrain. Claude makes every decision. Choice 2 — Run both in parallel (recommended to start): Keep the existing scheduler running as before. Add a second scheduler that calls Claude every 30 minutes. Compare the two decisions. Trust builds. PRODUCTION SETUP — 4 consoles: Console 1 — Scheduler (rule-based, every 10 min) Start-VBAFCenterSchedule -CustomerID "TruckCompanyDK" Console 2 — Portal Start-VBAFCenterPortal Console 3 — Dashboard Start-VBAFCenterDashboard Console 4 — Claude Brain (every 30 min manually or via loop) while ($true) { Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" Start-Sleep -Seconds 1800 } REVIEWING DECISIONS: See what Claude decided recently: powershellGet-VBAFCenterClaudeBrainHistory -CustomerID "TruckCompanyDK" Compare Claude vs rule-based in run history: powershellGet-VBAFCenterRunHistory -CustomerID "TruckCompanyDK" History rows with Source: Claude are AI decisions. Rows without are rule-based. COST ESTIMATE: Each Invoke-VBAFCenterClaudeBrain call uses roughly 800-1200 tokens. Every 30 min = 48 calls per day per customer 48 calls × 1000 tokens = 48.000 tokens per day At Claude Sonnet pricing ≈ DKK 1-2 per day per customer At 10 customers ≈ DKK 10-20 per day = DKK 300-600 per month Well within what you charge for monthly subscription. 🙂 IF SOMETHING GOES WRONG: Claude API unreachable: powershellInvoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Verbose Shows the full prompt and exact error message. API key expired or invalid: powershellSet-VBAFCenterClaudeAPIKey -APIKey "sk-ant-new-key" Claude returns garbage JSON: The -Verbose flag shows exactly what Claude returned so you can debug the prompt. THE MIGRATION PATH: Today : Rule-based every 10 min (what you have now) Week 1-2 : Claude every 30 min (parallel — compare decisions) Month 2 : Claude every 10 min (Claude is the brain) Month 3+ : Rule-based as fallback only (Claude primary) Never remove the rule-based engine entirely — it is your safety net if the API is down. #> |