VBAF.Center.LearningEngine.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    VBAF-Center Phase 16 — Learning Engine
.DESCRIPTION
    Reads run history and dispatcher overrides to identify patterns,
    calculate agreement rates and suggest threshold improvements.

    VBAF gets smarter the longer you use it.
    After 30 days it knows your operation better than any new dispatcher.

    Functions:
      Start-VBAFCenterOverride — log a dispatcher override
      Get-VBAFCenterOverrideHistory — show all overrides for a customer
      Invoke-VBAFCenterLearnFromHistory — analyse history and produce report
      Get-VBAFCenterLearningReport — show latest learning report
      Clear-VBAFCenterLearningData — reset learning data for a customer
#>


$script:LearningPath  = Join-Path $env:USERPROFILE "VBAFCenter\learning"
$script:OverridePath  = Join-Path $env:USERPROFILE "VBAFCenter\overrides"
$script:HistoryPath   = Join-Path $env:USERPROFILE "VBAFCenter\history"

function Initialize-VBAFCenterLearningStore {
    if (-not (Test-Path $script:LearningPath))  { New-Item -ItemType Directory -Path $script:LearningPath  -Force | Out-Null }
    if (-not (Test-Path $script:OverridePath))  { New-Item -ItemType Directory -Path $script:OverridePath  -Force | Out-Null }
}

# ============================================================
# START-VBAFCENTEROVERRIDE — log dispatcher override
# ============================================================
function Start-VBAFCenterOverride {
    <#
    .SYNOPSIS
        Log a dispatcher override — when dispatcher disagrees with VBAF.
        Call this immediately after a run where the dispatcher chose differently.
    .EXAMPLE
        Start-VBAFCenterOverride -CustomerID "TruckCompanyDK" -VBAFAction 3 -DispatcherAction 2 -Reason "Situation not as bad as VBAF thinks"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [Parameter(Mandatory)] [ValidateRange(0,3)] [int] $VBAFAction,
        [Parameter(Mandatory)] [ValidateRange(0,3)] [int] $DispatcherAction,
        [string] $Reason = ""
    )

    Initialize-VBAFCenterLearningStore

    # Get the latest run from history to capture signal snapshot
    $latestFile = Get-ChildItem $script:HistoryPath -Filter "$CustomerID-*.json" |
                  Sort-Object LastWriteTime -Descending |
                  Select-Object -First 1

    $signals     = @()
    $weightedAvg = $null
    $avgSignal   = 0.0
    $timestamp   = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")

    if ($latestFile) {
        $h           = Get-Content $latestFile.FullName -Raw | ConvertFrom-Json
        $signals     = $h.Signals
        $weightedAvg = $h.WeightedAvg
        $avgSignal   = $h.AvgSignal
        $timestamp   = $h.Timestamp
    }

    $actionNames = @("Monitor","Reassign","Reroute","Escalate")

    $override = [PSCustomObject] @{
        CustomerID       = $CustomerID
        Timestamp        = $timestamp
        LoggedAt         = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        Signals          = $signals
        AvgSignal        = $avgSignal
        WeightedAvg      = $weightedAvg
        VBAFAction       = $VBAFAction
        VBAFActionName   = $actionNames[$VBAFAction]
        DispatcherAction = $DispatcherAction
        DispatcherActionName = $actionNames[$DispatcherAction]
        Reason           = $Reason
        Outcome          = "Pending"   # updated by next run analysis
    }

    $overrideFile = Join-Path $script:OverridePath "$CustomerID-overrides.json"

    # Load existing overrides or start fresh
    $overrides = @()
    if (Test-Path $overrideFile) {
        try {
            $existing = Get-Content $overrideFile -Raw | ConvertFrom-Json
            $overrides = @($existing)
        } catch { $overrides = @() }
    }

    $overrides += $override
    $overrides | ConvertTo-Json -Depth 5 | Set-Content $overrideFile -Encoding UTF8

    Write-Host ""
    Write-Host "Override logged!" -ForegroundColor Green
    Write-Host (" Customer : {0}" -f $CustomerID)                   -ForegroundColor White
    Write-Host (" VBAF recommended : {0} — {1}" -f $VBAFAction, $actionNames[$VBAFAction])           -ForegroundColor Yellow
    Write-Host (" Dispatcher chose : {0} — {1}" -f $DispatcherAction, $actionNames[$DispatcherAction]) -ForegroundColor Cyan
    if ($Reason -ne "") {
        Write-Host (" Reason : {0}" -f $Reason) -ForegroundColor White
    }
    Write-Host (" Total overrides : {0}" -f $overrides.Count) -ForegroundColor DarkGray
    Write-Host ""
}

# ============================================================
# GET-VBAFCENTEROVERRIDEHISTORY
# ============================================================
function Get-VBAFCenterOverrideHistory {
    <#
    .SYNOPSIS
        Show all logged dispatcher overrides for a customer.
    .EXAMPLE
        Get-VBAFCenterOverrideHistory -CustomerID "TruckCompanyDK"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [int] $Last = 20
    )

    Initialize-VBAFCenterLearningStore

    $overrideFile = Join-Path $script:OverridePath "$CustomerID-overrides.json"
    if (-not (Test-Path $overrideFile)) {
        Write-Host "No overrides logged yet for: $CustomerID" -ForegroundColor Yellow
        Write-Host "Use Start-VBAFCenterOverride to log dispatcher disagreements." -ForegroundColor DarkGray
        return
    }

    $overrides = @(Get-Content $overrideFile -Raw | ConvertFrom-Json)
    $recent    = $overrides | Select-Object -Last $Last

    Write-Host ""
    Write-Host ("Override History: {0} (last {1})" -f $CustomerID, $recent.Count) -ForegroundColor Cyan
    Write-Host (" {0,-22} {1,-12} {2,-12} {3}" -f "Timestamp","VBAF","Dispatcher","Reason") -ForegroundColor Yellow
    Write-Host (" {0}" -f ("-" * 75)) -ForegroundColor DarkGray

    foreach ($o in $recent) {
        $color = if ($o.DispatcherAction -lt $o.VBAFAction) { "Green" } `
                 elseif ($o.DispatcherAction -gt $o.VBAFAction) { "Red" } `
                 else { "White" }
        Write-Host (" {0,-22} {1,-12} {2,-12} {3}" -f `
            $o.Timestamp,
            $o.VBAFActionName,
            $o.DispatcherActionName,
            $o.Reason) -ForegroundColor $color
    }
    Write-Host ""
    return $overrides
}

# ============================================================
# INVOKE-VBAFCENTERLEARNFROMHISTORY
# ============================================================
function Invoke-VBAFCenterLearnFromHistory {
    <#
    .SYNOPSIS
        Analyse run history and overrides to produce a learning report.
        Identifies patterns and suggests threshold adjustments.
    .EXAMPLE
        Invoke-VBAFCenterLearnFromHistory -CustomerID "TruckCompanyDK" -Days 30
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [int] $Days = 30
    )

    Initialize-VBAFCenterLearningStore

    $cutoff = (Get-Date).AddDays(-$Days)

    # Load history
    $historyFiles = Get-ChildItem $script:HistoryPath -Filter "$CustomerID-*.json" |
                    Where-Object { $_.LastWriteTime -ge $cutoff } |
                    Sort-Object LastWriteTime

    if ($historyFiles.Count -eq 0) {
        Write-Host "No history found for: $CustomerID in the last $Days days." -ForegroundColor Yellow
        return
    }

    $runs = @($historyFiles | ForEach-Object { Get-Content $_.FullName -Raw | ConvertFrom-Json })

    # Load overrides
    $overrideFile = Join-Path $script:OverridePath "$CustomerID-overrides.json"
    $overrides    = @()
    if (Test-Path $overrideFile) {
        try { $overrides = @(Get-Content $overrideFile -Raw | ConvertFrom-Json) } catch {}
    }

    # --------------------------------------------------------
    # ANALYSIS
    # --------------------------------------------------------
    $totalRuns     = $runs.Count
    $totalOverrides = $overrides.Count

    # Action distribution
    $actionCounts = @{0=0; 1=0; 2=0; 3=0}
    foreach ($r in $runs) { $actionCounts[[int]$r.Action]++ }

    # Override analysis
    $dispatcherLower  = @($overrides | Where-Object { $_.DispatcherAction -lt $_.VBAFAction }).Count
    $dispatcherHigher = @($overrides | Where-Object { $_.DispatcherAction -gt $_.VBAFAction }).Count
    $dispatcherSame   = @($overrides | Where-Object { $_.DispatcherAction -eq $_.VBAFAction }).Count

    # Average signal per action bucket
    $actionAvgs = @{}
    for ($a = 0; $a -le 3; $a++) {
        $bucket = @($runs | Where-Object { [int]$_.Action -eq $a })
        if ($bucket.Count -gt 0) {
            $sum = 0.0
            foreach ($r in $bucket) { $sum += [double]$r.AvgSignal }
            $actionAvgs[$a] = [Math]::Round($sum / $bucket.Count, 4)
        }
    }

    # Red signal override rate
    $redOverrideRuns = @($runs | Where-Object { $_.OverrideApplied -eq $true }).Count
    $redOverridePct  = if ($totalRuns -gt 0) { [Math]::Round($redOverrideRuns / $totalRuns * 100, 1) } else { 0 }

    # Suggest threshold adjustments based on override patterns
    $suggestions = @()

    if ($dispatcherLower -gt ($totalOverrides * 0.6) -and $totalOverrides -ge 5) {
        $suggestions += "Dispatcher chose LOWER action than VBAF in $dispatcherLower of $totalOverrides overrides. Consider RAISING Action3 threshold — VBAF may be escalating too quickly."
    }

    if ($dispatcherHigher -gt ($totalOverrides * 0.6) -and $totalOverrides -ge 5) {
        $suggestions += "Dispatcher chose HIGHER action than VBAF in $dispatcherHigher of $totalOverrides overrides. Consider LOWERING thresholds — VBAF may be too relaxed."
    }

    if ($actionCounts[3] -gt ($totalRuns * 0.3)) {
        $suggestions += "Escalate fired in $($actionCounts[3]) of $totalRuns runs ($([Math]::Round($actionCounts[3]/$totalRuns*100,1))%). This seems high — consider raising Action3 threshold."
    }

    if ($actionCounts[0] -gt ($totalRuns * 0.8)) {
        $suggestions += "Monitor fired in $($actionCounts[0]) of $totalRuns runs ($([Math]::Round($actionCounts[0]/$totalRuns*100,1))%). System may be too relaxed for this customer."
    }

    if ($suggestions.Count -eq 0) {
        $suggestions += "No threshold adjustments suggested — system appears well calibrated."
    }

    # --------------------------------------------------------
    # BUILD REPORT
    # --------------------------------------------------------
    $report = [PSCustomObject] @{
        CustomerID       = $CustomerID
        GeneratedAt      = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        PeriodDays       = $Days
        TotalRuns        = $totalRuns
        TotalOverrides   = $totalOverrides
        ActionCounts     = [PSCustomObject]@{ Monitor=$actionCounts[0]; Reassign=$actionCounts[1]; Reroute=$actionCounts[2]; Escalate=$actionCounts[3] }
        ActionAvgSignals = [PSCustomObject]@{ Monitor=$actionAvgs[0]; Reassign=$actionAvgs[1]; Reroute=$actionAvgs[2]; Escalate=$actionAvgs[3] }
        DispatcherLower  = $dispatcherLower
        DispatcherHigher = $dispatcherHigher
        DispatcherSame   = $dispatcherSame
        RedOverrideRuns  = $redOverrideRuns
        RedOverridePct   = $redOverridePct
        Suggestions      = $suggestions
    }

    # Save report
    $reportFile = Join-Path $script:LearningPath "$CustomerID-learning.json"
    $report | ConvertTo-Json -Depth 5 | Set-Content $reportFile -Encoding UTF8

    # --------------------------------------------------------
    # DISPLAY REPORT
    # --------------------------------------------------------
    $actionNames = @("Monitor","Reassign","Reroute","Escalate")

    Write-Host ""
    Write-Host ("Learning Report: {0}" -f $CustomerID) -ForegroundColor Cyan
    Write-Host (" Period : last {0} days" -f $Days) -ForegroundColor White
    Write-Host (" Generated : {0}" -f $report.GeneratedAt) -ForegroundColor White
    Write-Host ""
    Write-Host " Run Summary:" -ForegroundColor Yellow
    Write-Host (" Total runs : {0}" -f $totalRuns) -ForegroundColor White

    for ($a = 0; $a -le 3; $a++) {
        $pct = if ($totalRuns -gt 0) { [Math]::Round($actionCounts[$a] / $totalRuns * 100, 1) } else { 0 }
        $avg = if ($actionAvgs.ContainsKey($a)) { "avg signal {0}" -f $actionAvgs[$a] } else { "no runs" }
        $color = @("Green","Yellow","DarkYellow","Red")[$a]
        Write-Host (" {0,-10} : {1,3} runs ({2,5}%) {3}" -f $actionNames[$a], $actionCounts[$a], $pct, $avg) -ForegroundColor $color
    }

    Write-Host ""
    Write-Host " Override Summary:" -ForegroundColor Yellow

    if ($totalOverrides -eq 0) {
        Write-Host " No overrides logged yet." -ForegroundColor DarkGray
        Write-Host " Use Start-VBAFCenterOverride to log dispatcher disagreements." -ForegroundColor DarkGray
    } else {
        Write-Host (" Total overrides : {0}" -f $totalOverrides) -ForegroundColor White
        Write-Host (" Dispatcher lower : {0} (dispatcher less alarmed than VBAF)" -f $dispatcherLower) -ForegroundColor Green
        Write-Host (" Dispatcher higher : {0} (dispatcher more alarmed than VBAF)" -f $dispatcherHigher) -ForegroundColor Red
        Write-Host (" Same action : {0} (agreed on level but logged reason)" -f $dispatcherSame) -ForegroundColor White
    }

    Write-Host ""
    Write-Host (" RED signal overrides : {0} runs ({1}%)" -f $redOverrideRuns, $redOverridePct) -ForegroundColor White
    Write-Host ""
    Write-Host " Suggestions:" -ForegroundColor Yellow

    foreach ($s in $suggestions) {
        Write-Host (" -> {0}" -f $s) -ForegroundColor Cyan
    }

    Write-Host ""
    Write-Host " Apply suggested thresholds with:" -ForegroundColor DarkGray
    Write-Host (" Set-VBAFCenterActionThresholds -CustomerID ""{0}"" -Action1 X -Action2 Y -Action3 Z" -f $CustomerID) -ForegroundColor DarkGray
    Write-Host ""

    return $report
}

# ============================================================
# GET-VBAFCENTERLEARNINGREPORT — show saved report
# ============================================================
function Get-VBAFCenterLearningReport {
    <#
    .SYNOPSIS
        Show the latest saved learning report for a customer.
        Run Invoke-VBAFCenterLearnFromHistory first to generate it.
    .EXAMPLE
        Get-VBAFCenterLearningReport -CustomerID "TruckCompanyDK"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID
    )

    Initialize-VBAFCenterLearningStore

    $reportFile = Join-Path $script:LearningPath "$CustomerID-learning.json"
    if (-not (Test-Path $reportFile)) {
        Write-Host "No learning report found for: $CustomerID" -ForegroundColor Yellow
        Write-Host "Run Invoke-VBAFCenterLearnFromHistory first." -ForegroundColor DarkGray
        return
    }

    $report = Get-Content $reportFile -Raw | ConvertFrom-Json

    Write-Host ""
    Write-Host ("Learning Report: {0} (saved {1})" -f $CustomerID, $report.GeneratedAt) -ForegroundColor Cyan
    Write-Host (" Period : last {0} days Runs : {1} Overrides : {2}" -f `
        $report.PeriodDays, $report.TotalRuns, $report.TotalOverrides) -ForegroundColor White
    Write-Host ""
    Write-Host " Suggestions:" -ForegroundColor Yellow
    foreach ($s in $report.Suggestions) {
        Write-Host (" -> {0}" -f $s) -ForegroundColor Cyan
    }
    Write-Host ""

    return $report
}

# ============================================================
# CLEAR-VBAFCENTERLEARNINGDATA
# ============================================================
function Clear-VBAFCenterLearningData {
    <#
    .SYNOPSIS
        Reset all learning data for a customer — overrides and reports.
        Use when starting fresh after major operational changes.
    .EXAMPLE
        Clear-VBAFCenterLearningData -CustomerID "TruckCompanyDK"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID
    )

    $overrideFile = Join-Path $script:OverridePath "$CustomerID-overrides.json"
    $reportFile   = Join-Path $script:LearningPath "$CustomerID-learning.json"

    $removed = 0
    if (Test-Path $overrideFile) { Remove-Item $overrideFile -Force; $removed++ }
    if (Test-Path $reportFile)   { Remove-Item $reportFile   -Force; $removed++ }

    Write-Host ""
    if ($removed -gt 0) {
        Write-Host ("Learning data cleared for: {0}" -f $CustomerID) -ForegroundColor Green
    } else {
        Write-Host ("No learning data found for: {0}" -f $CustomerID) -ForegroundColor Yellow
    }
    Write-Host ""
}

# ============================================================
# LOAD MESSAGE
# ============================================================
Write-Host "VBAF-Center Phase 16 loaded [Learning Engine]" -ForegroundColor Cyan
Write-Host " Start-VBAFCenterOverride — log dispatcher override"       -ForegroundColor White
Write-Host " Get-VBAFCenterOverrideHistory — show all overrides"            -ForegroundColor White
Write-Host " Invoke-VBAFCenterLearnFromHistory — analyse and produce report"    -ForegroundColor White
Write-Host " Get-VBAFCenterLearningReport — show latest report"            -ForegroundColor White
Write-Host " Clear-VBAFCenterLearningData — reset learning data"           -ForegroundColor White
Write-Host ""