Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

127 lines
4.3 KiB
Go

package quality
import (
"fmt"
"sort"
"strings"
)
// SignalFeedback ties one rerank signal to its observed effect on
// agent-confirmed-useful results. Source: the existing `feedback`
// tool's per-symbol useful / not-needed / missing counters.
type SignalFeedback struct {
Signal string `json:"signal"`
Weight float64 `json:"current_weight"`
UsefulHits int `json:"useful_hits"`
MissedHits int `json:"missed_hits"`
// SuggestedWeight is the tuner's recommendation; the operator
// decides whether to apply it. >Weight = the signal is producing
// useful results and should be amplified; <Weight = it's
// adding noise.
SuggestedWeight float64 `json:"suggested_weight"`
Reasoning string `json:"reasoning"`
}
// SuggestWeights consumes the per-signal feedback rows + a global
// nudge factor and emits a per-signal suggestion. Pure analysis —
// the rerank pipeline isn't mutated; the caller applies via
// `.gortex.yaml::search.weights`.
//
// Algorithm:
// - signals with useful_hits > missed_hits get nudged UP by
// min(0.1, (useful - missed) / 100 * nudge)
// - signals with missed_hits > useful_hits get nudged DOWN by
// the same formula
// - signals with neither (no data) keep their current weight and
// report "insufficient data"
//
// Nudge is the per-call cap; 1.0 means "max ±0.1 per call",
// 0.5 means "half of that". Keeping nudges small makes tuning a
// gradient process rather than a wholesale weight swap.
func SuggestWeights(rows []SignalFeedback, nudge float64) []SignalFeedback {
if nudge <= 0 {
nudge = 1.0
}
out := make([]SignalFeedback, 0, len(rows))
for _, r := range rows {
delta := 0.0
reason := ""
switch {
case r.UsefulHits == 0 && r.MissedHits == 0:
reason = "insufficient data (no useful / missed records this window)"
case r.UsefulHits > r.MissedHits:
diff := float64(r.UsefulHits - r.MissedHits)
delta = clamp(diff/100.0*nudge, 0, 0.1)
reason = fmt.Sprintf("useful_hits=%d > missed=%d → nudge up by %.3f",
r.UsefulHits, r.MissedHits, delta)
case r.MissedHits > r.UsefulHits:
diff := float64(r.MissedHits - r.UsefulHits)
delta = -clamp(diff/100.0*nudge, 0, 0.1)
reason = fmt.Sprintf("missed_hits=%d > useful=%d → nudge down by %.3f",
r.MissedHits, r.UsefulHits, -delta)
default:
reason = fmt.Sprintf("useful_hits == missed_hits (%d) → no change", r.UsefulHits)
}
r.SuggestedWeight = r.Weight + delta
if r.SuggestedWeight < 0 {
r.SuggestedWeight = 0
}
r.Reasoning = reason
out = append(out, r)
}
// Stable display order: biggest absolute suggested change first
// so the operator sees the action items at the top.
sort.SliceStable(out, func(i, j int) bool {
return absVal(out[i].SuggestedWeight-out[i].Weight) > absVal(out[j].SuggestedWeight-out[j].Weight)
})
return out
}
// RenderTuningMarkdown produces the operator-facing report. Columns:
// signal, current weight, useful / missed counts, suggested weight,
// reasoning. Tail summary: count of nudges with abs(delta) > 0.05
// so the operator knows whether anything substantial is on the
// table.
func RenderTuningMarkdown(rows []SignalFeedback) string {
var b strings.Builder
fmt.Fprintln(&b, "# Rerank weight-tuning suggestion")
fmt.Fprintln(&b)
fmt.Fprintln(&b, "_Each row's `suggested_weight` is a calculated nudge from the current `.gortex.yaml::search.weights` value, based on the feedback log. **Operator decides whether to apply** — no automatic mutation._")
fmt.Fprintln(&b)
fmt.Fprintln(&b, "| signal | current | useful | missed | suggested | Δ | reasoning |")
fmt.Fprintln(&b, "|--------|--------:|-------:|-------:|----------:|--:|-----------|")
substantial := 0
for _, r := range rows {
delta := r.SuggestedWeight - r.Weight
if absVal(delta) >= 0.05 {
substantial++
}
fmt.Fprintf(&b, "| %s | %.3f | %d | %d | %.3f | %+.3f | %s |\n",
r.Signal, r.Weight, r.UsefulHits, r.MissedHits,
r.SuggestedWeight, delta, r.Reasoning)
}
fmt.Fprintln(&b)
fmt.Fprintf(&b, "**Substantial nudges (|Δ| ≥ 0.05): %d / %d signals.**\n",
substantial, len(rows))
return b.String()
}
// --- math helpers ---------------------------------------------------
func clamp(v, lo, hi float64) float64 {
if v < lo {
return lo
}
if v > hi {
return hi
}
return v
}
func absVal(v float64) float64 {
if v < 0 {
return -v
}
return v
}