VBAF.Center.TMSSimulator.Standard.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    VBAF-Center TMS Simulator Standard — 4 Signals
.DESCRIPTION
    Standard 4-signal simulator for customers scoring 16-25
    on the VBAF-Center Assessment (Standard complexity).

    Signals:
      Signal1 — Empty Driving %
      Signal2 — On-Time Delivery %
      Signal3 — Cost Per Trip (DKK)
      Signal4 — Route Efficiency %

    Events:
      WeatherEvent — On-Time drops, Route drops
      TrafficJam — Cost rises, ETA drops
      VehicleBreakdown — Cost spikes, Empty rises
      FuelPriceSpike — Cost spikes

    Modes:
      Shadow — every 30 minutes
      GoLive — every 15 minutes
      Autonomy — every 10 minutes
      RealTime — continuous demo

    Functions:
      Get-VBAFTMSStdAllSignals — get all 4 signals
      Invoke-VBAFTMSStdEvent — fire a named or random event
      Show-VBAFTMSStdStatus — dashboard view
      Invoke-VBAFTMSStdDayReplay — full day 48 episodes
      Start-VBAFTMSStdSchedule — run in chosen mode
#>


# ============================================================
# STATE
# ============================================================
$script:TMSStdState = @{
    ActiveEvent     = $null
    EventRoundsLeft = 0
    Bases = @{
        EmptyDriving    = 32.0
        OnTimeDelivery  = 74.0
        CostPerTrip     = 1800.0
        RouteEfficiency = 78.0
    }
}

# ============================================================
# TIME OF DAY CURVE
# ============================================================
function Get-TMSStdTimeCurve {
    param([int]$Hour)
    switch ($Hour) {
        {$_ -lt 5}      { return 0.3 }
        {$_ -in 5..7}   { return 0.6 }
        {$_ -in 8..11}  { return 1.0 }
        {$_ -in 12..14} { return 0.9 }
        {$_ -in 15..17} { return 0.8 }
        {$_ -in 18..20} { return 0.6 }
        {$_ -in 21..23} { return 0.4 }
        default          { return 0.5 }
    }
}

# ============================================================
# GET-VBAFTMSSTDALLSIGNALS
# ============================================================
function Get-VBAFTMSStdAllSignals {

    $hour  = (Get-Date).Hour
    $curve = Get-TMSStdTimeCurve -Hour $hour
    $noise = (Get-Random -Minimum -50 -Maximum 50) / 10.0
    $event = $script:TMSStdState.ActiveEvent

    $empty = $script:TMSStdState.Bases.EmptyDriving + ((1.0 - $curve) * 20) + $noise
    if ($event -eq "VehicleBreakdown") { $empty += 15 }
    if ($event -eq "TrafficJam")       { $empty += 8  }

    $ontime = $script:TMSStdState.Bases.OnTimeDelivery + (($curve - 0.5) * 10) + $noise
    if ($event -eq "WeatherEvent")     { $ontime -= 20 }
    if ($event -eq "TrafficJam")       { $ontime -= 15 }
    if ($event -eq "VehicleBreakdown") { $ontime -= 10 }

    $cost = $script:TMSStdState.Bases.CostPerTrip + ((1.0 - $curve) * 200) + ($noise * 10)
    if ($event -eq "VehicleBreakdown") { $cost += 600 }
    if ($event -eq "TrafficJam")       { $cost += 300 }
    if ($event -eq "FuelPriceSpike")   { $cost += 500 }

    $route = $script:TMSStdState.Bases.RouteEfficiency + (($curve - 0.5) * 8) + $noise
    if ($event -eq "WeatherEvent") { $route -= 15 }
    if ($event -eq "TrafficJam")   { $route -= 20 }

    $signals = @{
        EmptyDriving    = [Math]::Round([Math]::Max(0,   [Math]::Min(100,  $empty)),  1)
        OnTimeDelivery  = [Math]::Round([Math]::Max(0,   [Math]::Min(100,  $ontime)), 1)
        CostPerTrip     = [Math]::Round([Math]::Max(800, [Math]::Min(4000, $cost)),   0)
        RouteEfficiency = [Math]::Round([Math]::Max(0,   [Math]::Min(100,  $route)),  1)
        Timestamp       = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        ActiveEvent     = if ($event) { $event } else { "None" }
    }

    if ($script:TMSStdState.EventRoundsLeft -gt 0) {
        $script:TMSStdState.EventRoundsLeft--
        if ($script:TMSStdState.EventRoundsLeft -eq 0) {
            Write-Host (" [TMS] Event '{0}' cleared." -f $script:TMSStdState.ActiveEvent) -ForegroundColor DarkGray
            $script:TMSStdState.ActiveEvent = $null
        }
    }

    return $signals
}

# ============================================================
# INVOKE-VBAFTMSSTDEVENT
# ============================================================
function Invoke-VBAFTMSStdEvent {
    param(
        [ValidateSet("WeatherEvent","TrafficJam","VehicleBreakdown","FuelPriceSpike","Random")]
        [string] $Event = "Random",
        [int] $Rounds = 4
    )

    if ($Event -eq "Random") {
        $events = @("WeatherEvent","TrafficJam","VehicleBreakdown","FuelPriceSpike")
        $Event  = $events[(Get-Random -Maximum 4)]
    }

    $script:TMSStdState.ActiveEvent     = $Event
    $script:TMSStdState.EventRoundsLeft = $Rounds

    $description = switch ($Event) {
        "WeatherEvent"     { "On-Time drops, Route Efficiency drops"  }
        "TrafficJam"       { "Cost rises, On-Time drops"              }
        "VehicleBreakdown" { "Cost spikes, Empty Driving rises"       }
        "FuelPriceSpike"   { "Cost spikes across all trips"           }
    }

    Write-Host ""
    Write-Host " [TMS EVENT FIRED]"              -ForegroundColor Red
    Write-Host (" Event : {0}" -f $Event)       -ForegroundColor Yellow
    Write-Host (" Effect : {0}" -f $description) -ForegroundColor Yellow
    Write-Host (" Rounds : {0}" -f $Rounds)      -ForegroundColor Yellow
    Write-Host ""
}

# ============================================================
# SHOW-VBAFTMSSTDSTATUS
# ============================================================
function Show-VBAFTMSStdStatus {

    $s = Get-VBAFTMSStdAllSignals

    $ec = if ($s.EmptyDriving -gt 40)    { "Red"   } elseif ($s.EmptyDriving -gt 25)    { "Yellow" } else { "Green" }
    $oc = if ($s.OnTimeDelivery -lt 70)  { "Red"   } elseif ($s.OnTimeDelivery -lt 85)  { "Yellow" } else { "Green" }
    $cc = if ($s.CostPerTrip -gt 2500)   { "Red"   } elseif ($s.CostPerTrip -gt 2000)   { "Yellow" } else { "Green" }
    $rc = if ($s.RouteEfficiency -lt 65) { "Red"   } elseif ($s.RouteEfficiency -lt 80) { "Yellow" } else { "Green" }

    Write-Host ""
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host " | VBAF TMS Simulator Standard |" -ForegroundColor Cyan
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host (" | Time : {0,-27}|" -f $s.Timestamp)  -ForegroundColor White
    Write-Host (" | Event : {0,-27}|" -f $s.ActiveEvent) -ForegroundColor Yellow
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host (" | Empty Driving : {0,6} % |" -f $s.EmptyDriving)    -ForegroundColor $ec
    Write-Host (" | On-Time Delivery : {0,6} % |" -f $s.OnTimeDelivery)  -ForegroundColor $oc
    Write-Host (" | Cost Per Trip : {0,6} DKK |" -f $s.CostPerTrip)     -ForegroundColor $cc
    Write-Host (" | Route Efficiency : {0,6} % |" -f $s.RouteEfficiency) -ForegroundColor $rc
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host ""

    return $s
}

# ============================================================
# INVOKE-VBAFTMSSTDDAYREPLAY
# ============================================================
function Invoke-VBAFTMSStdDayReplay {
    param(
        [string] $CustomerID = "TruckCompanyDK",
        [switch] $FireEvents
    )

    Write-Host ""
    Write-Host " [TMS Std] Starting Day Replay — 48 episodes, 4 signals" -ForegroundColor Cyan
    Write-Host (" [TMS Std] CustomerID: {0}" -f $CustomerID) -ForegroundColor White
    Write-Host ""

    $results = @()

    for ($episode = 1; $episode -le 48; $episode++) {

        $simulatedHour   = [Math]::Floor(($episode - 1) / 2)
        $simulatedMinute = if (($episode % 2) -eq 0) { 30 } else { 0 }
        $timeLabel       = "{0:00}:{1:00}" -f $simulatedHour, $simulatedMinute

        if ($FireEvents -and ($episode -in @(10, 24, 38))) {
            Invoke-VBAFTMSStdEvent -Event "Random" -Rounds 4
        }

        $s = Get-VBAFTMSStdAllSignals

        $result = [PSCustomObject]@{
            Episode         = $episode
            Time            = $timeLabel
            EmptyDriving    = $s.EmptyDriving
            OnTimeDelivery  = $s.OnTimeDelivery
            CostPerTrip     = $s.CostPerTrip
            RouteEfficiency = $s.RouteEfficiency
            Event           = $s.ActiveEvent
        }

        $results += $result

        $ec = if ($s.EmptyDriving -gt 40)   { "Red" } elseif ($s.EmptyDriving -gt 25)   { "Yellow" } else { "Green" }
        $oc = if ($s.OnTimeDelivery -lt 70) { "Red" } elseif ($s.OnTimeDelivery -lt 85) { "Yellow" } else { "Green" }

        Write-Host (" Ep {0,2} {1} Empty: " -f $episode, $timeLabel) -NoNewline -ForegroundColor White
        Write-Host ("{0,5} %" -f $s.EmptyDriving) -NoNewline -ForegroundColor $ec
        Write-Host " OnTime: " -NoNewline -ForegroundColor White
        Write-Host ("{0,5} %" -f $s.OnTimeDelivery) -NoNewline -ForegroundColor $oc
        Write-Host (" Cost: {0,5} DKK Route: {1,5} %" -f $s.CostPerTrip, $s.RouteEfficiency) -NoNewline -ForegroundColor White
        if ($s.ActiveEvent -ne "None") {
            Write-Host (" [{0}]" -f $s.ActiveEvent) -ForegroundColor Red
        } else {
            Write-Host ""
        }

        Start-Sleep -Milliseconds 150
    }

    $avgEmpty  = [Math]::Round(($results | Measure-Object -Property EmptyDriving    -Average).Average, 1)
    $avgOnTime = [Math]::Round(($results | Measure-Object -Property OnTimeDelivery  -Average).Average, 1)
    $avgCost   = [Math]::Round(($results | Measure-Object -Property CostPerTrip     -Average).Average, 0)
    $avgRoute  = [Math]::Round(($results | Measure-Object -Property RouteEfficiency -Average).Average, 1)
    $alerts    = ($results | Where-Object { $_.EmptyDriving -gt 40 -or $_.OnTimeDelivery -lt 70 -or $_.CostPerTrip -gt 2500 }).Count

    Write-Host ""
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host " | Day Replay Summary |" -ForegroundColor Cyan
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host (" | Avg Empty Driving : {0,5} % |" -f $avgEmpty)  -ForegroundColor White
    Write-Host (" | Avg On-Time Delivery : {0,5} % |" -f $avgOnTime) -ForegroundColor White
    Write-Host (" | Avg Cost Per Trip : {0,5} DKK |" -f $avgCost)   -ForegroundColor White
    Write-Host (" | Avg Route Efficiency : {0,5} % |" -f $avgRoute)  -ForegroundColor White
    Write-Host (" | Alert Episodes : {0,5} |" -f $alerts)    -ForegroundColor Yellow
    Write-Host " +--------------------------------------+" -ForegroundColor Cyan
    Write-Host ""

    return $results
}

# ============================================================
# START-VBAFTMSSTDSCHEDULE
# ============================================================
function Start-VBAFTMSStdSchedule {
    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [ValidateSet("Shadow","GoLive","Autonomy","RealTime")]
        [string] $Mode = "Shadow",
        [int] $MaxRuns = 10
    )

    $intervalSeconds = switch ($Mode) {
        "Shadow"   { 1800 }
        "GoLive"   { 900  }
        "Autonomy" { 600  }
        "RealTime" { 5    }
    }

    Write-Host ""
    Write-Host " [TMS Std] Starting Schedule"                              -ForegroundColor Cyan
    Write-Host (" Mode : {0}" -f $Mode)                               -ForegroundColor White
    Write-Host (" Interval : {0} seconds" -f $intervalSeconds)            -ForegroundColor White
    Write-Host (" MaxRuns : {0}" -f $MaxRuns)                            -ForegroundColor White
    Write-Host " Press Ctrl+C to stop."                                    -ForegroundColor Yellow
    Write-Host ""

    for ($run = 1; $run -le $MaxRuns; $run++) {
        Write-Host (" [Run {0}/{1}]" -f $run, $MaxRuns) -ForegroundColor DarkCyan
        Show-VBAFTMSStdStatus | Out-Null
        if (($run % 7) -eq 0) { Invoke-VBAFTMSStdEvent -Event "Random" -Rounds 3 }
        if ($run -lt $MaxRuns) {
            Write-Host (" Next run in {0} seconds..." -f $intervalSeconds) -ForegroundColor DarkGray
            Start-Sleep -Seconds $intervalSeconds
        }
    }

    Write-Host ""
    Write-Host " [TMS Std] Schedule complete." -ForegroundColor Cyan
    Write-Host ""
}

# ============================================================
# LOAD MESSAGE
# ============================================================
Write-Host ""
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
Write-Host " | VBAF-Center TMS Simulator Standard |" -ForegroundColor Cyan
Write-Host " | 4 Signals · 4 Events · 4 Modes |" -ForegroundColor Cyan
Write-Host " | For Assessment score 16-25 |" -ForegroundColor Cyan
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
Write-Host ""
Write-Host " Signals : EmptyDriving · OnTimeDelivery · CostPerTrip · RouteEfficiency" -ForegroundColor White
Write-Host " Events : WeatherEvent · TrafficJam · VehicleBreakdown · FuelPriceSpike" -ForegroundColor White
Write-Host " Modes : Shadow(30m) · GoLive(15m) · Autonomy(10m) · RealTime"          -ForegroundColor White
Write-Host ""
Write-Host " Quick start:"                                                                          -ForegroundColor Yellow
Write-Host " Show-VBAFTMSStdStatus"                                                                 -ForegroundColor Green
Write-Host " Invoke-VBAFTMSStdDayReplay -CustomerID 'TruckCompanyDK' -FireEvents"                    -ForegroundColor Green
Write-Host " Start-VBAFTMSStdSchedule -CustomerID 'TruckCompanyDK' -Mode Shadow -MaxRuns 10"       -ForegroundColor Green
Write-Host ""