package staffing import "github.com/shopspring/decimal" // The portfolio optimizer maximizes total assignment value. The value of assigning a candidate to a // requirement is a stated function of the frozen engine fit and the requirement priority: // // value(i, j) = engineTotal(i, j) * priorityMultiplier(j) // // where engineTotal is the deterministic 0 to 100 score and priorityMultiplier maps the requirement's // backing opportunity dollar value into a bounded multiplier. The value is then scaled to an int64 so the // minimum cost flow solve is integer exact and FIPS clean (no float in the objective). Scoring stays // decimal in the engine; only this objective is an integer, and it is allocation, not scoring. // PriorityMultiplier maps an opportunity dollar value to a bounded multiplier in [minM, maxM]: // // minM + clamp(value / reference, 0, maxM - minM) // // With the defaults (reference 5,000,000, min 1, max 3): a 5,000,000 dollar opportunity maps to 2x, a // 10,000,000 or larger to the 3x cap, a tiny opportunity to just above 1x, and a zero or negative value to // 1x. A non positive reference disables the dollar weighting (every requirement gets the minimum // multiplier), which keeps fit the sole driver rather than dividing by zero. func PriorityMultiplier(value, reference, minM, maxM decimal.Decimal) decimal.Decimal { if reference.LessThanOrEqual(decimal.Zero) { return minM } span := maxM.Sub(minM) if span.LessThan(decimal.Zero) { span = decimal.Zero } add := value.Div(reference) if add.LessThan(decimal.Zero) { add = decimal.Zero } if add.GreaterThan(span) { add = span } return minM.Add(add) } // ScaledValue computes the int64 objective value for one candidate requirement pair: // // round( engineTotal * priorityMultiplier * scale ) // // shopspring rounds half away from zero, which is deterministic, so identical inputs yield byte identical // integers across runs and machines. engineTotal is in [0,100] and the multiplier in [1,3], so with the // default scale of 1,000,000 the result is at most 3e8, far inside int64. func ScaledValue(engineTotal, multiplier decimal.Decimal, scale int64) int64 { return engineTotal.Mul(multiplier).Mul(decimal.NewFromInt(scale)).Round(0).IntPart() }