Private/Invoke-JevMockDecision.ps1

function Invoke-JevMockDecision {
    <#
    Creates deterministic, schema-compatible answers for local demonstrations.
    This is intentionally simple. It makes the pipeline mechanics testable,
    but it is not intended to imitate Jev's quality or calibration.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $State,

        [Parameter(Mandatory)]
        [System.Collections.IDictionary] $Questions,

        [Parameter(Mandatory)]
        [string] $Model
    )

    $text = Get-JevStateText -State $State
    $answers = [ordered] @{}

    foreach ($questionEntry in $Questions.GetEnumerator()) {
        $name = [string] $questionEntry.Key
        if ($null -eq $questionEntry.Value) {
            throw "Question '$name' cannot be null."
        }
        $question = ConvertTo-JevDictionary -Value $questionEntry.Value
        $type = ([string] $question.type).ToLowerInvariant()

        switch ($type) {
            'noul' {
                $instructions = if ($question.Contains('instructions')) { [string] $question.instructions } else { '' }
                $questionText = "$name $instructions $text".ToLowerInvariant()
                $signal = $false

                if ($questionText -match 'security|critical|attack|breach|credential|unauthori[sz]ed|brute|injection|malware|ransom') {
                    $signal = $text -match 'security|attack|breach|credential|unauthori[sz]ed|brute|injection|malware|ransom|privilege|token'
                }
                elseif ($questionText -match 'churn|leav|cancel|retention') {
                    $signal = $text -match 'cancel|leav|switch|competitor|unacceptable|losing (?:sales|business)|refund|downgrade'
                }
                elseif ($questionText -match 'urgent|active|threat|risk') {
                    $signal = $text -match 'urgent|asap|immediately|blocked|outage|down|critical|losing|breach|attack'
                }
                else {
                    $signal = $text -match 'yes|true|critical|urgent|blocked|breach|attack'
                }

                $probability = if ($signal) { 0.93 } else { 0.08 }
                $answers[$name] = [pscustomobject] [ordered] @{
                    type = 'noul'
                    noul = $probability
                }
            }

            'choice' {
                $criteria = ConvertTo-JevDictionary -Value $question.criteria
                $keys = @($criteria.Keys | ForEach-Object { [string] $_ })
                $lowerText = $text.ToLowerInvariant()
                $selectedIndex = 0
                $matched = $false

                for ($index = 0; $index -lt $keys.Count; $index++) {
                    $key = $keys[$index].ToLowerInvariant()
                    $keyPattern = [regex]::Escape($key)

                    $keyMatch = switch -Regex ($key) {
                        'auth|credential|access|identity|permission' { $lowerText -match 'auth|login|password|credential|token|permission|unauthori[sz]ed' }
                        'network|connect|timeout|transport' { $lowerText -match 'network|timeout|connection|connect|dns|socket|refused|unreachable' }
                        'syntax|parse|format|json|command' { $lowerText -match 'syntax|parse|invalid json|unexpected token|parser|command not found' }
                        'restart|reboot' { $lowerText -match 'restart|reboot|start over' }
                        'status|health|check' { $lowerText -match 'status|health|is .*up|running' }
                        'log|tail|diagnos' { $lowerText -match 'log|logs|diagnos|trace' }
                        'deploy|release|publish' { $lowerText -match 'deploy|release|publish|ship' }
                        'help|unknown|other' { $false }
                        default { $lowerText -match $keyPattern }
                    }

                    if ($keyMatch) {
                        $selectedIndex = $index
                        $matched = $true
                        break
                    }
                }

                if (-not $matched) {
                    $unknownIndex = [array]::IndexOf($keys, 'unknown')
                    if ($unknownIndex -lt 0) { $unknownIndex = [array]::IndexOf($keys, 'help') }
                    if ($unknownIndex -ge 0) { $selectedIndex = $unknownIndex }
                }

                $selectedChoice = $keys[$selectedIndex]
                $answers[$name] = [pscustomobject] [ordered] @{
                    type          = 'choice'
                    choice        = $selectedChoice
                    confidence    = if ($matched) { 0.91 } else { 0.56 }
                    probabilities = New-JevProbabilityDistribution -Keys $keys -SelectedIndex $selectedIndex
                }
            }

            'score' {
                $levels = @($question.criteria)
                $lowerText = $text.ToLowerInvariant()
                $selectedIndex = 0

                if ($levels.Count -gt 1 -and $lowerText -match 'urgent|asap|immediately|blocked|outage|down|critical|losing') {
                    $selectedIndex = $levels.Count - 1
                }
                elseif ($levels.Count -gt 2 -and $lowerText -match 'soon|today|important|problem|error|failed') {
                    $selectedIndex = $levels.Count - 2
                }

                $levelKeys = @($levels | ForEach-Object { [string] ([array]::IndexOf($levels, $_)) })
                $legend = [ordered] @{}
                for ($index = 0; $index -lt $levels.Count; $index++) {
                    $legend[[string] $index] = $levels[$index]
                }

                $answers[$name] = [pscustomobject] [ordered] @{
                    type          = 'score'
                    score         = [math]::Round([double] $selectedIndex, 2)
                    confidence    = if ($selectedIndex -eq 0) { 0.62 } else { 0.86 }
                    legend        = $legend
                    probabilities = New-JevProbabilityDistribution -Keys $levelKeys -SelectedIndex $selectedIndex
                }
            }

            default {
                throw "Unsupported Jev question type '$type'."
            }
        }
    }

    return [pscustomobject] [ordered] @{
        model   = $Model
        answers = $answers
        usage   = [pscustomobject] [ordered] @{
            input_tokens  = 0
            output_tokens = 0
        }
    }
}