package matching import ( "context" "github.com/jackc/pgx/v5" "github.com/shopspring/decimal" "github.com/missionworkforce/missionmatch/internal/modules/matching/engine" ) // rerankModelKey is the champion display order re ranker read at slate generation to order the members. It is // the same model_key the MAG trainer (cmd/train-model -kind reranker) fits and the evaluator promotes. const rerankModelKey = "slate_display_order" // championRerankWeights reads the champion display order re ranker's utility weights keyed by feature name plus // its version, from the global MAG coefficient store on the caller's tx. It is BEST EFFORT: on any absence or // error it returns nil weights and version 0, and the caller keeps the frozen engine order (the re ranker only // ever permutes an already scored slate, never blocks generating it). The prior only champion (version 1) holds // the engine component weights, so the order is unchanged until a LEARNED challenger is promoted; generateSlate // therefore applies the reorder only for a learned champion (version above 1), keeping the prior only order byte // identical to the engine order. func championRerankWeights(ctx context.Context, tx pgx.Tx) (map[string]float64, int) { rows, err := tx.Query(ctx, ` SELECT c.feature_name, c.coefficient::float8, m.version FROM aedt_learned_models m JOIN aedt_model_coefficients c ON c.model_key = m.model_key AND c.version = m.version WHERE m.model_key = $1 AND m.champion = true AND m.output_kind = 'display_order'`, rerankModelKey) if err != nil { return nil, 0 } defer rows.Close() weights := map[string]float64{} version := 0 for rows.Next() { var name string var coef float64 var v int if err := rows.Scan(&name, &coef, &v); err != nil { return nil, 0 } weights[name] = coef version = v } if rows.Err() != nil || len(weights) == 0 { return nil, 0 } return weights, version } // rerankUtility is a candidate's display utility under the re ranker weights: the learned weight on each frozen // engine sub score. The display order is this utility descending. It is NEVER a candidate score; it only orders // an already scored slate, and the frozen engine Total remains the score shown. The feature names match the // trainer's RerankFeatureOrder and map one to one onto the engine MatchResult sub scores. func rerankUtility(weights map[string]float64, res engine.MatchResult) float64 { term := func(name string, d decimal.Decimal) float64 { v, _ := d.Float64() return weights[name] * v } return term("clearance_score", res.Clearance) + term("skills_score", res.Skills) + term("certs_score", res.Certifications) + term("customer_score", res.CustomerExperience) + term("experience_score", res.Experience) + term("location_score", res.Location) }