Files
weichen 45f21e84c7 feat(jevdecide): product shadow client with offline eval gates
Add typed jevdecide HTTP client (circuit, metrics) and coordinator
DecideAssist wiring behind JEV_DECIDE_MODE (default off). Offline eval
and synthetic soak evidence gate realtime/profile before assist rollout.
2026-09-23 17:40:40 +00:00

80 lines
2.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package jevdecide is an HTTP client for the local jev-service typed decide API.
// Product wiring is fail-open: timeouts, auth errors, and circuit opens fall back
// to existing deterministic rules. See .doc/ops/short-term/2026-09-24-jev-product-shadow.md.
package jevdecide
import "strings"
// Mode controls product-side integration.
type Mode string
const (
ModeOff Mode = "off" // zero behavior change (default)
ModeShadow Mode = "shadow" // rules route; jev recorded for comparison
ModeAssist Mode = "assist" // jev may augment selected decisions (with rule floor)
)
// Question is one typed decide question (choice / noul / numeric).
type Question struct {
Type string `json:"type"`
Question string `json:"question"`
Options []string `json:"options,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
}
// Answer is one typed decide answer.
type Answer struct {
Type string `json:"type"`
Choice string `json:"choice,omitempty"`
Confidence float64 `json:"confidence"`
Probabilities map[string]float64 `json:"probabilities,omitempty"`
Yes bool `json:"yes,omitempty"`
Noul float64 `json:"noul,omitempty"`
Value float64 `json:"value,omitempty"`
Error string `json:"error,omitempty"`
}
// Response is the /v1/decide envelope.
type Response struct {
Model string `json:"model"`
Answers map[string]Answer `json:"answers"`
}
// Margin returns top1−top2 probability gap (0 if fewer than two options).
func (a Answer) Margin() float64 {
if len(a.Probabilities) < 2 {
return a.Confidence
}
var top1, top2 float64
for _, p := range a.Probabilities {
if p >= top1 {
top2 = top1
top1 = p
} else if p > top2 {
top2 = p
}
}
return top1 - top2
}
// Reliable reports whether the answer is usable for assist routing.
func (a Answer) Reliable(minConf, minMargin float64) bool {
if a.Error != "" || strings.TrimSpace(a.Choice) == "" {
return false
}
return a.Confidence >= minConf && a.Margin() >= minMargin
}
// ProfileBucket maps a complexity score to a closed enum for jev choice.
func ProfileBucket(score int) string {
switch {
case score >= 60:
return "high"
case score >= 30:
return "mid"
default:
return "low"
}
}