package learn import "sort" // Result values a bid label can carry, mirroring the bid_outcomes.result CHECK. Only win and loss are decided // labels a fit consumes; pending and no_bid are dropped by BuildDataset (an undecided bid is not a training // example). These are the exact strings the ledger stores, so a raw scan maps straight onto them. const ( ResultWin = "win" ResultLoss = "loss" ResultNoBid = "no_bid" ResultPending = "pending" ) // LabeledRow is one bid from the ledger: its decided Result and the named feature values of the segment the // bid was for (market structure only, NEVER a protected attribute or a per candidate score). BuildDataset // keeps only the rows whose Result is win or loss; everything else is an undecided bid and is dropped. type LabeledRow struct { Result string // one of the Result constants; only win and loss survive into the matrix Features map[string]float64 // named segment feature values for this bid } // IsDecided reports whether a result is a decided label (a win or a loss). A pending or no_bid row carries no // outcome to learn from and is excluded from the training matrix. func IsDecided(result string) bool { return result == ResultWin || result == ResultLoss } // BuildDataset assembles the labeled feature matrix from the decided bids. It drops every undecided row // (pending, no_bid, or an unrecognized result), maps win to label 1 and loss to label 0, and returns the // feature matrix x (one row per decided bid, one column per feature), the labels y, and the feature names in a // STABLE order (the sorted union of every feature key across the decided rows). A row missing a feature the // union contains contributes 0.0 for that column, so a ragged input still yields a rectangular matrix; the // stable ordering means the same ledger always produces the same column layout, which the trainer relies on to // pair a fitted coefficient with its feature name. No database, no I/O: this is pure over an in memory rowset. func BuildDataset(rows []LabeledRow) (x [][]float64, y []int, featureNames []string) { // The sorted union of feature names across the decided rows: the stable column layout. nameSet := map[string]struct{}{} for _, r := range rows { if !IsDecided(r.Result) { continue } for name := range r.Features { nameSet[name] = struct{}{} } } featureNames = make([]string, 0, len(nameSet)) for name := range nameSet { featureNames = append(featureNames, name) } sort.Strings(featureNames) for _, r := range rows { if !IsDecided(r.Result) { continue } row := make([]float64, len(featureNames)) for j, name := range featureNames { row[j] = r.Features[name] // a missing feature is the zero column, the documented default } x = append(x, row) if r.Result == ResultWin { y = append(y, 1) } else { y = append(y, 0) } } return x, y, featureNames }