Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/aioe/compute.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/aioe/compute.go |
| Original SHA-256 | c942482a3b509fead5f0c61795c94b4d6debbd6641c6b0c60f9b2087b3bb20f7 |
| Git blob SHA | 9fe5b6e46a4be79673e8488d9297a905c41bcb40 |
| Reading-copy SHA-256 | c942482a3b509fead5f0c61795c94b4d6debbd6641c6b0c60f9b2087b3bb20f7 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/aioe/compute.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/aioe/compute.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓240 lines · 7.0 KB
package aioe
import (
"fmt"
"github.com/shopspring/decimal"
)
// Params bounds every update cycle. All decimal so the math is bit stable.
type Params struct {
// Eta scales how strongly window evidence pulls a weight (the learning rate).
Eta decimal.Decimal
// Beta is the exponential moving average smoothing toward the evidence target.
Beta decimal.Decimal
// MaxStep is the hard cap on the evidence driven change of one dimension in one cycle.
MaxStep decimal.Decimal
// Floor and Ceiling are the hard clamps every dimension weight must stay inside.
Floor decimal.Decimal
Ceiling decimal.Decimal
// MinEvents is the minimum outcome count in the window before any update happens.
MinEvents int
// WindowDays is the trailing outcome window the updater consumes.
WindowDays int
}
// DefaultParams returns the shipped bounds: gentle steps, wide clamps, a quarter of outcomes
// before the first move.
func DefaultParams() Params {
return Params{
Eta: decimal.RequireFromString("0.10"),
Beta: decimal.RequireFromString("0.30"),
MaxStep: decimal.RequireFromString("0.02"),
Floor: decimal.RequireFromString("0.05"),
Ceiling: decimal.RequireFromString("0.40"),
MinEvents: 25,
WindowDays: 90,
}
}
// Event is one outcome the updater consumes: the normalized signal in [-1, 1] and the served per
// dimension raw scores (0 to 100) that the app denormalized into outcome_events.metadata at
// capture time. The updater reads outcome_events ONLY; this struct is the whole input.
type Event struct {
Signal decimal.Decimal
DimScores map[string]decimal.Decimal
}
// DimChange is one audited weight change: the dimension, both values, and the human rationale.
type DimChange struct {
Dimension string
Old decimal.Decimal
New decimal.Decimal
Rationale string
}
var (
one = decimal.NewFromInt(1)
negOne = decimal.NewFromInt(-1)
fifty = decimal.NewFromInt(50)
epsilon = decimal.New(1, -4) // 0.0001, the 4dp storage grain
)
// ComputeUpdate is the pure, deterministic weight update: bounded exponential moving average
// steps on each dimension driven by window evidence, hard clamped to [Floor, Ceiling] and MaxStep,
// then the exact sum of 1.0000 restored. Same inputs always produce the same outputs: iteration
// follows the canonical Dimensions order, all math is decimal, and no clock or randomness enters.
// It returns the new weights, the per dimension changes (empty when nothing moved), and the number
// of outcomes consumed.
//
// Evidence per dimension is the mean over events of signal * position, where position normalizes
// the served dimension score to [-1, 1] around the 50 midpoint. A positive outcome on a decision
// where a dimension scored high is evidence the dimension helps; a positive outcome where it
// scored low is evidence it matters less. The EMA target is old * (1 + Eta * evidence) and the
// applied step is Beta of the way there, so one wild window cannot yank a weight.
func ComputeUpdate(old map[string]decimal.Decimal, events []Event, p Params) (map[string]decimal.Decimal, []DimChange, int) {
used := len(events)
out := make(map[string]decimal.Decimal, len(Dimensions))
for _, d := range Dimensions {
out[d] = old[d]
}
if used < p.MinEvents {
return out, nil, used
}
evidence := make(map[string]decimal.Decimal, len(Dimensions))
for _, dim := range Dimensions {
sum := decimal.Zero
n := 0
for _, ev := range events {
score, ok := ev.DimScores[dim]
if !ok {
continue
}
pos := score.Sub(fifty).Div(fifty)
if pos.GreaterThan(one) {
pos = one
}
if pos.LessThan(negOne) {
pos = negOne
}
sum = sum.Add(ev.Signal.Mul(pos))
n++
}
if n > 0 {
evidence[dim] = sum.Div(decimal.NewFromInt(int64(n)))
} else {
evidence[dim] = decimal.Zero
}
}
// Bounded EMA step per dimension, clamped to MaxStep and [Floor, Ceiling].
for _, dim := range Dimensions {
step := p.Beta.Mul(p.Eta).Mul(evidence[dim]).Mul(old[dim])
if step.GreaterThan(p.MaxStep) {
step = p.MaxStep
}
if step.LessThan(p.MaxStep.Neg()) {
step = p.MaxStep.Neg()
}
v := old[dim].Add(step)
if v.LessThan(p.Floor) {
v = p.Floor
}
if v.GreaterThan(p.Ceiling) {
v = p.Ceiling
}
out[dim] = v
}
restoreExactSum(out, p)
// Round to the 4dp storage grain and repair any rounding residue deterministically.
for _, dim := range Dimensions {
out[dim] = out[dim].Round(4)
}
repairRounding(out, p)
var changes []DimChange
for _, dim := range Dimensions {
if out[dim].Equal(old[dim].Round(4)) {
continue
}
changes = append(changes, DimChange{
Dimension: dim,
Old: old[dim].Round(4),
New: out[dim],
Rationale: fmt.Sprintf(
"Mean outcome evidence %s over %d outcomes moved %s from %s to %s under bounds: max step %s, floor %s, ceiling %s, exact sum restored.",
evidence[dim].Round(4).String(), used, dim,
old[dim].Round(4).String(), out[dim].String(),
p.MaxStep.String(), p.Floor.String(), p.Ceiling.String()),
})
}
if len(changes) == 0 {
// Nothing moved after clamping and rounding: report no update at all.
for _, d := range Dimensions {
out[d] = old[d]
}
}
return out, changes, used
}
// restoreExactSum spreads the residual (1 minus the clamped sum) across dimensions that still have
// slack toward the needed direction, walking the canonical order, bounded passes. The evidence
// step is what MaxStep bounds; this restore is the allocation identity, kept inside the clamps.
func restoreExactSum(w map[string]decimal.Decimal, p Params) {
for pass := 0; pass < 8; pass++ {
sum := decimal.Zero
for _, d := range Dimensions {
sum = sum.Add(w[d])
}
diff := one.Sub(sum)
if diff.IsZero() {
return
}
var slack []string
for _, d := range Dimensions {
if diff.IsPositive() && w[d].LessThan(p.Ceiling) {
slack = append(slack, d)
}
if diff.IsNegative() && w[d].GreaterThan(p.Floor) {
slack = append(slack, d)
}
}
if len(slack) == 0 {
return
}
share := diff.Div(decimal.NewFromInt(int64(len(slack)))).Round(6)
remaining := diff
for i, d := range slack {
take := share
if i == len(slack)-1 {
take = remaining
}
v := w[d].Add(take)
if v.GreaterThan(p.Ceiling) {
v = p.Ceiling
}
if v.LessThan(p.Floor) {
v = p.Floor
}
remaining = remaining.Sub(v.Sub(w[d]))
w[d] = v
}
}
}
// repairRounding fixes the residue 4dp rounding leaves so the stored profile sums to exactly
// 1.0000. The residue lands on dimensions in canonical order, one 0.0001 grain at a time, always
// inside the clamps.
func repairRounding(w map[string]decimal.Decimal, p Params) {
for guard := 0; guard < 1000; guard++ {
sum := decimal.Zero
for _, d := range Dimensions {
sum = sum.Add(w[d])
}
diff := one.Sub(sum)
if diff.IsZero() {
return
}
grain := epsilon
if diff.IsNegative() {
grain = epsilon.Neg()
}
moved := false
for _, d := range Dimensions {
v := w[d].Add(grain)
if v.GreaterThan(p.Ceiling) || v.LessThan(p.Floor) {
continue
}
w[d] = v
moved = true
break
}
if !moved {
return
}
}
}