Private/Invoke-JevMockDecision.ps1
|
<#
.SYNOPSIS Creates deterministic, schema-compatible answers without an API call. .DESCRIPTION Makes pipeline behavior testable but does not imitate Jev's quality or calibration. .PARAMETER State The input state used by the mock's simple text rules. .PARAMETER Questions The normalized, named Jev questions to answer. .PARAMETER Model The model name to include in the mock response. #> function Invoke-JevMockDecision { [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 } } } |