VBAF.Center.DailyBriefing.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    VBAF-Center — Daily Briefing
.DESCRIPTION
    Generates a clean HTML daily briefing from ClaudeBrain
    and rule-based history. Opens automatically in browser.

    Designed for the dispatcher — not the developer.
    No PowerShell. No console. Just open the browser.

    Functions:
      Export-VBAFCenterDailyBriefing — generate and open HTML briefing
      Get-VBAFCenterBriefingPath — show where briefings are saved
#>


$script:BriefingPath = Join-Path $env:USERPROFILE "VBAFCenter\briefings"

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

# ============================================================
# FIX DANISH CHARACTERS FOR HTML OUTPUT
# ============================================================
function Repair-VBAFCenterDanishHTML {
    param([string] $Text)
    if (-not $Text) { return "" }
    # Fix garbled UTF-8 read as Windows-1252
    try {
        $bytes = [System.Text.Encoding]::GetEncoding(1252).GetBytes($Text)
        $fixed = [System.Text.Encoding]::UTF8.GetString($bytes)
        if ($fixed -notmatch "\?\?") { $Text = $fixed }
    } catch {}
    # Fix common Mistral ascii substitutions
    $Text = $Text -replace "\baa\b", "aa"
    $Text = $Text -replace "tomkoersel",      "tomkørsel"
    $Text = $Text -replace "omgaaende",        "omgående"
    $Text = $Text -replace "oejeblikkeligt",   "øjeblikkeligt"
    $Text = $Text -replace "oejeblikkelig",    "øjeblikkelig"
    $Text = $Text -replace "omgaaende",        "omgående"
    $Text = $Text -replace "gennemgaa",        "gennemgå"
    $Text = $Text -replace "gennemgaar",       "gennemgår"
    $Text = $Text -replace "ruteplanlaegning", "ruteplanlægning"
    $Text = $Text -replace "planlaegning",     "planlægning"
    $Text = $Text -replace "flaadekapacitet",  "flådekapacitet"
    $Text = $Text -replace "flaaden",          "flåden"
    $Text = $Text -replace "flaade",           "flåde"
    $Text = $Text -replace "leveringspraecision", "leveringspræcision"
    $Text = $Text -replace "praecision",       "præcision"
    $Text = $Text -replace "ressourceallokering", "ressourceallokering"
    $Text = $Text -replace "oekonomi",         "økonomi"
    $Text = $Text -replace "kritiske",         "kritiske"
    $Text = $Text -replace "vaerste",          "værste"
    $Text = $Text -replace "vaer ",            "vær "
    $Text = $Text -replace "hoejeste",         "højeste"
    $Text = $Text -replace "hoej",             "høj"
    $Text = $Text -replace "Hoej",             "Høj"
    $Text = $Text -replace "hojeste",          "højeste"
    $Text = $Text -replace "daarligste",       "dårligste"
    $Text = $Text -replace "daarlig",          "dårlig"
    $Text = $Text -replace "naermer",          "nærmer"
    $Text = $Text -replace "naeste",           "næste"
    $Text = $Text -replace "saerlig",          "særlig"
    $Text = $Text -replace "beordr",           "beordr"
    $Text = $Text -replace "tilpasninger",     "tilpasninger"
    $Text = $Text -replace "kapacitetsjusteringer", "kapacitetsjusteringer"
    $Text = $Text -replace "midlertidige",     "midlertidige"
    $Text = $Text -replace "gentagne",         "gentagne"
    $Text = $Text -replace "koersler",         "kørsler"
    $Text = $Text -replace "koersel",          "kørsel"
    $Text = $Text -replace "braendef",         "brændstof"
    $Text = $Text -replace "braendstof",       "brændstof"
    $Text = $Text -replace "udledning",        "udledning"
    $Text = $Text -replace "effektivitet",     "effektivitet"
    $Text = $Text -replace "prioriter",        "prioritér"
    $Text = $Text -replace "anmod",            "anmod"
    $Text = $Text -replace "reducere",         "reducere"
    $Text = $Text -replace "lastbiler",        "lastbiler"
    $Text = $Text -replace "leveringer",       "leveringer"
    $Text = $Text -replace "faldende",         "faldende"
    $Text = $Text -replace "H\?j","Høj" -replace "H.j","Høj"
    return $Text
}

# ============================================================
# GET-VBAFCENTERBRIEFINGPATH
# ============================================================
function Get-VBAFCenterBriefingPath {
    Write-Host ("Briefings saved to: {0}" -f $script:BriefingPath) -ForegroundColor Cyan
    Get-ChildItem $script:BriefingPath -Filter "*.html" -ErrorAction SilentlyContinue |
        Sort-Object LastWriteTime -Descending |
        Select-Object -First 10 |
        ForEach-Object { Write-Host (" {0}" -f $_.Name) -ForegroundColor White }
}

# ============================================================
# EXPORT-VBAFCENTERDAILYBRIEFING
# ============================================================
function Export-VBAFCenterDailyBriefing {
    <#
    .SYNOPSIS
        Generate HTML daily briefing and open in browser.
        Reads last 24h of history + latest AI Brain result.
    .EXAMPLE
        Export-VBAFCenterDailyBriefing -CustomerID "NordLogistik"
        Export-VBAFCenterDailyBriefing -CustomerID "NordLogistik" -Provider "Mistral" -OpenBrowser
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [string] $Provider    = "Mistral",
        [switch] $OpenBrowser,
        [switch] $RunAIFirst
    )

    Initialize-VBAFCenterBriefingStore

    Write-Host ""
    Write-Host (" Generating daily briefing: {0}" -f $CustomerID) -ForegroundColor Cyan

    # Load customer profile
    $profilePath = Join-Path $env:USERPROFILE "VBAFCenter\customers\$CustomerID.json"
    if (-not (Test-Path $profilePath)) {
        Write-Host (" Customer not found: {0}" -f $CustomerID) -ForegroundColor Red
        return
    }
    $profile = Get-Content $profilePath -Raw | ConvertFrom-Json

    # Run AI Brain first if requested
    $latestAI = $null
    if ($RunAIFirst) {
        Write-Host " Running AI Brain first..." -ForegroundColor DarkGray
        if (Get-Command Invoke-VBAFCenterClaudeBrain -ErrorAction SilentlyContinue) {
            $latestAI = Invoke-VBAFCenterClaudeBrain -CustomerID $CustomerID -Provider $Provider -SuppressCrisis
        }
    }

    # Load history — last 24 hours
    $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history"
    $cutoff      = (Get-Date).AddHours(-24)
    $allHistory  = @()
    $aiHistory   = @()

    if (Test-Path $historyPath) {
        $files = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" |
                 Sort-Object LastWriteTime -Descending |
                 Select-Object -First 200

        foreach ($f in $files) {
            try {
                $h = Get-Content $f.FullName -Raw | ConvertFrom-Json
                $allHistory += $h
                if ($h.Source -like "AI-*") { $aiHistory += $h }
            } catch {}
        }
    }

    $last24h   = @($allHistory | Where-Object {
        try { [DateTime]::Parse($_.Timestamp) -ge $cutoff } catch { $false }
    })
    $aiLast24h = @($aiHistory  | Where-Object {
        try { [DateTime]::Parse($_.Timestamp) -ge $cutoff } catch { $false }
    })

    # Get latest AI result
    if (-not $latestAI -and $aiHistory.Count -gt 0) {
        $latestAI = $aiHistory | Sort-Object Timestamp | Select-Object -Last 1
    }

    # Statistics
    $actionNames  = @("Monitor","Reassign","Reroute","Escalate")
    $actionColors = @("#1D9E75","#EF9F27","#EF6B27","#E24B4A")

    $totalRuns    = $last24h.Count
    $actionCounts = @{0=0;1=0;2=0;3=0}
    foreach ($h in $last24h) { try { $actionCounts[[int]$h.Action]++ } catch {} }

    $avgSignal = if ($last24h.Count -gt 0) {
        [Math]::Round(($last24h | ForEach-Object { try { [double]$_.WeightedAvg } catch { 0 } } | Measure-Object -Average).Average, 3)
    } else { 0 }

    $maxRed = if ($last24h.Count -gt 0) {
        ($last24h | ForEach-Object { try { [int]$_.RedSignalCount } catch { 0 } } | Measure-Object -Maximum).Maximum
    } else { 0 }

    # Load daily log
    $logPath    = Join-Path $env:USERPROFILE "VBAFCenter\dailylog"
    $logEntries = @()
    $logFile    = Join-Path $logPath "$CustomerID-$(Get-Date -Format 'yyyyMMdd').log"
    if (Test-Path $logFile) {
        $logEntries = Get-Content $logFile -Encoding UTF8 -ErrorAction SilentlyContinue
    }

    # Dominant action
    $dominantAction = 0
    $dominantCount  = 0
    foreach ($a in 0..3) {
        if ($actionCounts[$a] -gt $dominantCount) {
            $dominantCount  = $actionCounts[$a]
            $dominantAction = $a
        }
    }

    # Latest rule-based run
    $latestRule = $allHistory | Where-Object { $_.Source -notlike "AI-*" } |
                  Sort-Object Timestamp | Select-Object -Last 1

    # Build signal cards HTML
    $signalCardsHTML = ""
    if ($latestRule -and $latestRule.Signals) {
        $signalNames = @(
            "Empty Driving %","On-Time Delivery %","Cost Per Trip DKK",
            "Route Efficiency %","ETA Accuracy %","CO2 Per Trip kg",
            "POD Completion %","Driver Performance %","Fleet Availability %","Capacity Util %"
        )
        $signals = @($latestRule.Signals)
        for ($i = 0; $i -lt [Math]::Min($signals.Count, $signalNames.Count); $i++) {
            $norm  = try { [double]$signals[$i] } catch { 0 }
            $pct   = [int]($norm * 100)
            $color = if ($norm -gt 0.75) { "#E24B4A" } elseif ($norm -gt 0.40) { "#EF9F27" } else { "#1D9E75" }
            $bg    = if ($norm -gt 0.75) { "#fde8e8" } elseif ($norm -gt 0.40) { "#fef3e2" } else { "#e8f8f2" }
            $signalCardsHTML += @"
        <div class="signal-card" style="border-left:4px solid $color;background:$bg">
          <div class="signal-name">$($signalNames[$i])</div>
          <div class="signal-value" style="color:$color">$pct%</div>
          <div class="signal-bar"><div class="signal-fill" style="width:$pct%;background:$color"></div></div>
        </div>
"@

        }
    }

    # Build AI section HTML
    $aiSectionHTML = ""
    if ($latestAI) {
        $aiAction    = try { [int]$latestAI.Action } catch { 0 }
        $aiColor     = $actionColors[$aiAction]
        $aiName      = $actionNames[$aiAction]
        $aiReason    = Repair-VBAFCenterDanishHTML -Text ([string]$latestAI.ActionReason)
        $aiInstruct  = Repair-VBAFCenterDanishHTML -Text ([string]$latestAI.ActionCommand)
        $aiPattern   = Repair-VBAFCenterDanishHTML -Text ([string]$latestAI.Pattern)
        $aiConf      = Repair-VBAFCenterDanishHTML -Text ([string]$latestAI.Confidence)
        $aiTime      = [string]$latestAI.Timestamp
        $aiProvider  = [string]$latestAI.Source -replace "^AI-",""

        $patternHTML = ""
        if ($aiPattern -and $aiPattern -ne "") {
            $patternHTML = "<div class='pattern-box'>🔍 Pattern: $aiPattern</div>"
        }

        $aiSectionHTML = @"
      <div class="ai-card">
        <div class="ai-header">
          <span class="ai-badge">AI Brain — $aiProvider</span>
          <span class="ai-time">$aiTime</span>
        </div>
        <div class="ai-action" style="color:$aiColor;border-left:4px solid $aiColor">
          Action $aiAction — $aiName
          <span class="conf-badge">Confidence: $aiConf</span>
        </div>
        <div class="ai-reason"><strong>Reason:</strong> $aiReason</div>
        <div class="ai-instruction" style="border-left:4px solid $aiColor">
          <strong>Instruction:</strong> $aiInstruct
        </div>
        $patternHTML
      </div>
"@

    } else {
        $aiSectionHTML = "<div class='ai-card'><p style='color:#888'>No AI Brain result available. Run Invoke-VBAFCenterClaudeBrain first.</p></div>"
    }

    # Build action distribution bars
    $distHTML = ""
    foreach ($a in 0..3) {
        $pct   = if ($totalRuns -gt 0) { [int]($actionCounts[$a] / $totalRuns * 100) } else { 0 }
        $color = $actionColors[$a]
        $name  = $actionNames[$a]
        $distHTML += @"
        <div class="dist-row">
          <span class="dist-label">$name</span>
          <div class="dist-bar"><div class="dist-fill" style="width:$pct%;background:$color"></div></div>
          <span class="dist-pct">$($actionCounts[$a]) runs ($pct%)</span>
        </div>
"@

    }

    # Build log HTML
    $logHTML = ""
    if ($logEntries.Count -gt 0) {
        foreach ($line in ($logEntries | Select-Object -Last 10)) {
            $logColor   = if ($line -like "*Escalate*") { "#E24B4A" } elseif ($line -like "*Reroute*") { "#EF6B27" } elseif ($line -like "*Reassign*") { "#EF9F27" } else { "#1D9E75" }
            $fixedLine  = Repair-VBAFCenterDanishHTML -Text $line
            $logHTML   += "<div class='log-line' style='color:$logColor'>$fixedLine</div>"
        }
    } else {
        $logHTML = "<div class='log-line' style='color:#888'>No AI decisions logged today yet.</div>"
    }

    # Date and day
    $dayNames   = @("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday")
    $todayName  = $dayNames[[int](Get-Date).DayOfWeek]
    $dateStr    = (Get-Date).ToString("d MMMM yyyy")
    $domColor   = $actionColors[$dominantAction]
    $domName    = $actionNames[$dominantAction]
    $domPct     = if ($totalRuns -gt 0) { [int]($actionCounts[$dominantAction] / $totalRuns * 100) } else { 0 }

    # ============================================================
    # BUILD HTML
    # ============================================================
    $html = @"
<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VBAF Daily Briefing — $($profile.CompanyName)</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Arial,sans-serif;background:#f4f4f0;color:#2C2C2A;padding:0}
.header{background:#2C2C2A;color:#fff;padding:20px 24px;display:flex;justify-content:space-between;align-items:center}
.header-left h1{font-size:18px;font-weight:500}
.header-left p{font-size:13px;color:#aaa;margin-top:4px}
.header-right{text-align:right}
.header-right .day{font-size:22px;font-weight:500}
.header-right .date{font-size:13px;color:#aaa}
.content{padding:20px 24px;max-width:1200px;margin:0 auto}
.section-title{font-size:13px;font-weight:500;color:#5F5E5A;text-transform:uppercase;letter-spacing:0.5px;margin:20px 0 10px}
.summary-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:8px}
.sum-card{background:#fff;border-radius:10px;padding:14px 16px;border:0.5px solid #D3D1C7}
.sum-card .label{font-size:11px;color:#888;margin-bottom:4px}
.sum-card .value{font-size:22px;font-weight:500}
.sum-card .sub{font-size:11px;color:#888;margin-top:2px}
.dominant-card{background:#fff;border-radius:10px;padding:16px;margin-bottom:16px;border:0.5px solid #D3D1C7;display:flex;align-items:center;gap:16px}
.dominant-action{font-size:32px;font-weight:700;padding:8px 20px;border-radius:8px;color:#fff}
.dominant-text h2{font-size:16px;font-weight:500}
.dominant-text p{font-size:13px;color:#666;margin-top:4px}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.card{background:#fff;border-radius:10px;padding:16px;border:0.5px solid #D3D1C7}
.signal-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.signal-card{padding:10px 12px;border-radius:8px;background:#f8f8f6}
.signal-name{font-size:11px;color:#666;margin-bottom:4px}
.signal-value{font-size:16px;font-weight:500;margin-bottom:4px}
.signal-bar{background:#e8e8e4;border-radius:4px;height:4px;overflow:hidden}
.signal-fill{height:4px;border-radius:4px;transition:width 0.3s}
.dist-row{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.dist-label{font-size:12px;color:#444;min-width:80px}
.dist-bar{flex:1;background:#f0f0ec;border-radius:4px;height:8px;overflow:hidden}
.dist-fill{height:8px;border-radius:4px}
.dist-pct{font-size:11px;color:#888;min-width:100px;text-align:right}
.ai-card{background:#fff;border-radius:10px;padding:16px;border:0.5px solid #D3D1C7;margin-bottom:16px}
.ai-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.ai-badge{background:#EEEDFE;color:#26215C;font-size:11px;font-weight:500;padding:3px 10px;border-radius:20px}
.ai-time{font-size:11px;color:#888}
.ai-action{font-size:18px;font-weight:500;padding:10px 14px;border-radius:8px;background:#f8f8f6;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center}
.conf-badge{font-size:11px;font-weight:400;color:#666;background:#fff;padding:2px 8px;border-radius:10px;border:0.5px solid #ddd}
.ai-reason{font-size:13px;color:#444;margin-bottom:10px;line-height:1.5}
.ai-instruction{font-size:13px;padding:10px 14px;background:#f8f8f6;border-radius:8px;margin-bottom:10px;line-height:1.5}
.pattern-box{font-size:12px;color:#26215C;background:#EEEDFE;padding:8px 12px;border-radius:8px;border-left:4px solid #AFA9EC}
.log-line{font-size:11px;font-family:monospace;padding:4px 0;border-bottom:0.5px solid #f0f0ec;line-height:1.6}
.footer{text-align:center;color:#888;font-size:12px;padding:20px 0;border-top:0.5px solid #D3D1C7;margin-top:20px}
.refresh-btn{display:inline-block;margin-top:8px;padding:6px 16px;background:#2C2C2A;color:#fff;border-radius:6px;font-size:12px;cursor:pointer;text-decoration:none}
</style>
</head>
<body>

<div class="header">
  <div class="header-left">
    <h1>VBAF Daily Briefing</h1>
    <p>$($profile.CompanyName) · $($profile.BusinessType)</p>
  </div>
  <div class="header-right">
    <div class="day">$todayName</div>
    <div class="date">$dateStr</div>
  </div>
</div>

<div class="content">

  <div class="section-title">Last 24 Hours — Overview</div>

  <div class="summary-cards">
    <div class="sum-card">
      <div class="label">Total runs</div>
      <div class="value">$totalRuns</div>
      <div class="sub">rule-based checks</div>
    </div>
    <div class="sum-card">
      <div class="label">Average signal</div>
      <div class="value">$avgSignal</div>
      <div class="sub">0.0 = perfect · 1.0 = crisis</div>
    </div>
    <div class="sum-card">
      <div class="label">Max red signals</div>
      <div class="value" style="color:#E24B4A">$maxRed</div>
      <div class="sub">in a single run</div>
    </div>
    <div class="sum-card">
      <div class="label">AI Brain runs</div>
      <div class="value" style="color:#26215C">$($aiLast24h.Count)</div>
      <div class="sub">via $Provider</div>
    </div>
  </div>

  <div class="dominant-card">
    <div class="dominant-action" style="background:$domColor">$domName</div>
    <div class="dominant-text">
      <h2>Most common action yesterday</h2>
      <p>$domName fired $dominantCount times ($domPct% of all runs)</p>
    </div>
  </div>

  <div class="section-title">AI Brain — Latest Assessment</div>
  $aiSectionHTML

  <div class="two-col">
    <div>
      <div class="section-title">Current Signal Status</div>
      <div class="card">
        <div class="signal-grid">
          $signalCardsHTML
        </div>
      </div>
    </div>
    <div>
      <div class="section-title">Action Distribution (last 24h)</div>
      <div class="card">
        $distHTML
      </div>
      <div class="section-title" style="margin-top:16px">AI Decision Log (today)</div>
      <div class="card">
        $logHTML
      </div>
    </div>
  </div>

</div>

<div class="footer">
  VBAF-Center v1.0 · $($profile.CompanyName) · Generated $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
  <br><a class="refresh-btn" href="javascript:location.reload()">Refresh</a>
</div>

</body>
</html>
"@


    # Save HTML
    $filename = "$CustomerID-briefing-$(Get-Date -Format 'yyyyMMdd_HHmm').html"
    $filepath = Join-Path $script:BriefingPath $filename
    $htmlBytes = [System.Text.Encoding]::UTF8.GetBytes($html); [System.IO.File]::WriteAllBytes($filepath, $htmlBytes)

    Write-Host (" Briefing saved: {0}" -f $filepath) -ForegroundColor Green

    # Also save as latest
    $latestPath = Join-Path $script:BriefingPath "$CustomerID-latest.html"
    $htmlBytes = [System.Text.Encoding]::UTF8.GetBytes($html); [System.IO.File]::WriteAllBytes($latestPath, $htmlBytes)
    Write-Host (" Latest: {0}" -f $latestPath) -ForegroundColor Green

    # Open in browser
    if ($OpenBrowser) {
        Start-Process $latestPath
        Write-Host " Opening in browser..." -ForegroundColor Cyan
    } else {
        Write-Host " Open with: Start-Process '$latestPath'" -ForegroundColor DarkGray
    }

    Write-Host ""
    return $latestPath
}

# ============================================================
# LOAD MESSAGE
# ============================================================
Write-Host ""
Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan
Write-Host " | VBAF-Center — Daily Briefing |" -ForegroundColor Cyan
Write-Host " | HTML report for dispatcher — opens in browser |" -ForegroundColor Cyan
Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan
Write-Host ""
Write-Host " Export-VBAFCenterDailyBriefing — generate HTML briefing"        -ForegroundColor White
Write-Host " Get-VBAFCenterBriefingPath — show saved briefings"          -ForegroundColor White
Write-Host ""
Write-Host " Quick start:" -ForegroundColor Yellow
Write-Host " Export-VBAFCenterDailyBriefing -CustomerID 'NordLogistik' -OpenBrowser" -ForegroundColor DarkGray
Write-Host " Export-VBAFCenterDailyBriefing -CustomerID 'NordLogistik' -RunAIFirst -OpenBrowser" -ForegroundColor DarkGray
Write-Host ""