Spaces:
Sleeping
Sleeping
| package main | |
| import ( | |
| // Script Imports | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "io/ioutil" | |
| "math/rand" | |
| "os" | |
| "slices" | |
| "sort" | |
| "strconv" | |
| "strings" | |
| "time" | |
| // MongoDB Imports | |
| "go.mongodb.org/mongo-driver/mongo" | |
| "go.mongodb.org/mongo-driver/mongo/options" | |
| ) | |
| type LineupData struct { | |
| Salary []int32 | |
| Projection []float64 | |
| Team []string | |
| Team_count []int32 | |
| Secondary []string | |
| Secondary_count []int32 | |
| Ownership []float64 | |
| Players [][]int32 | |
| } | |
| type Player struct { | |
| ID int32 `json:"id"` | |
| Name string `json:"name"` | |
| Team string `json:"team"` | |
| Position string `json:"position"` | |
| Salary int32 `json:"salary"` | |
| Projection float64 `json:"projection"` | |
| Ownership float64 `json:"ownership"` | |
| SalaryValue float64 `json:"salary_value"` | |
| ProjValue float64 `json:"proj_value"` | |
| OwnValue float64 `json:"own_value"` | |
| SortValue float64 `json:"sort_value"` | |
| Slate string `json:"slate"` | |
| } | |
| type PlayerSet struct { | |
| Players []Player `json:"players"` | |
| Maps struct { | |
| NameMap map[string]string `json:"name_map"` | |
| SalaryMap map[string]int32 `json:"salary_map"` | |
| ProjectionMap map[string]float64 `json:"projection_map"` | |
| OwnershipMap map[string]float64 `json:"ownership_map"` | |
| TeamMap map[string]string `json:"team_map"` | |
| } `json:"maps"` | |
| } | |
| type ProcessedData struct { | |
| PlayersMedian PlayerSet `json:"players_median"` | |
| } | |
| type PlayerData struct { | |
| Players []Player | |
| NameMap map[int]string | |
| } | |
| type StrengthResult struct { | |
| Index int | |
| Data LineupData | |
| Error error | |
| } | |
| type LineupDocument struct { | |
| Salary int32 `bson:"salary"` | |
| Projection float64 `bson:"proj"` | |
| Team string `bson:"Team"` | |
| Team_count int32 `bson:"Team_count"` | |
| Secondary string `bson:"Secondary"` | |
| Secondary_count int32 `bson:"Secondary_count"` | |
| Ownership float64 `bson:"Own"` | |
| QB int32 `bson:"QB"` | |
| RB1 int32 `bson:"RB1"` | |
| RB2 int32 `bson:"RB2"` | |
| WR1 int32 `bson:"WR1"` | |
| WR2 int32 `bson:"WR2"` | |
| WR3 int32 `bson:"WR3"` | |
| TE int32 `bson:"TE"` | |
| FLEX int32 `bson:"FLEX"` | |
| DST int32 `bson:"DST"` | |
| CreatedAt time.Time `bson:"created_at"` | |
| } | |
| func loadPlayerData() (*ProcessedData, error) { | |
| data, err := ioutil.ReadFile("fd_nfl_go/player_data.json") | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read in data: %v", err) | |
| } | |
| var processedData ProcessedData | |
| if err := json.Unmarshal(data, &processedData); err != nil { | |
| return nil, fmt.Errorf("failed to parse json: %v", err) | |
| } | |
| return &processedData, nil | |
| } | |
| func loadOptimals() (map[string]LineupData, error) { | |
| data, err := ioutil.ReadFile("fd_nfl_go/optimal_lineups.json") | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse optimals: %v", err) | |
| } | |
| type OptimalsJSON struct { | |
| Slate string `json:"slate"` | |
| Salary int32 `json:"salary"` | |
| Projection float64 `json:"projection"` | |
| Team string `json:"team"` | |
| Team_count int32 `json:"team_count"` | |
| Secondary string `json:"secondary"` | |
| Secondary_count int32 `json:"secondary_count"` | |
| Ownership float64 `json:"ownership"` | |
| Players []int32 `json:"players"` | |
| } | |
| var allOptimals []OptimalsJSON | |
| if err := json.Unmarshal(data, &allOptimals); err != nil { | |
| return nil, fmt.Errorf("failed to parse optimals JSON: %v", err) | |
| } | |
| optimalsBySlate := make(map[string]LineupData) | |
| for _, optimal := range allOptimals { | |
| if _, exists := optimalsBySlate[optimal.Slate]; !exists { | |
| optimalsBySlate[optimal.Slate] = LineupData{ | |
| Salary: []int32{}, | |
| Projection: []float64{}, | |
| Team: []string{}, | |
| Team_count: []int32{}, | |
| Secondary: []string{}, | |
| Secondary_count: []int32{}, | |
| Ownership: []float64{}, | |
| Players: [][]int32{}, | |
| } | |
| } | |
| slateData := optimalsBySlate[optimal.Slate] | |
| slateData.Salary = append(slateData.Salary, optimal.Salary) | |
| slateData.Projection = append(slateData.Projection, optimal.Projection) | |
| slateData.Team = append(slateData.Team, optimal.Team) | |
| slateData.Team_count = append(slateData.Team_count, optimal.Team_count) | |
| slateData.Secondary = append(slateData.Secondary, optimal.Secondary) | |
| slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count) | |
| slateData.Ownership = append(slateData.Ownership, optimal.Ownership) | |
| slateData.Players = append(slateData.Players, optimal.Players) | |
| optimalsBySlate[optimal.Slate] = slateData | |
| } | |
| return optimalsBySlate, nil | |
| } | |
| func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData { | |
| if len(optimals.Salary) == 0 { | |
| return results | |
| } | |
| // Simply append the optimal LineupData to existing results | |
| return append(results, optimals) | |
| } | |
| func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) { | |
| salaryMap := make(map[int32]int32) | |
| projMap := make(map[int32]float64) | |
| ownMap := make(map[int32]float64) | |
| teamMap := make(map[int32]string) | |
| for keyStr, value := range playerSet.Maps.SalaryMap { | |
| key, err := strconv.Atoi(keyStr) | |
| if err != nil { | |
| fmt.Printf("Error converting key %s: %v\n", keyStr, err) | |
| continue | |
| } | |
| salaryMap[int32(key)] = value | |
| } | |
| for keyStr, value := range playerSet.Maps.ProjectionMap { | |
| key, err := strconv.Atoi(keyStr) | |
| if err != nil { | |
| fmt.Printf("Error converting key %s: %v\n", keyStr, err) | |
| continue | |
| } | |
| projMap[int32(key)] = value | |
| } | |
| for keyStr, value := range playerSet.Maps.OwnershipMap { | |
| key, err := strconv.Atoi(keyStr) | |
| if err != nil { | |
| fmt.Printf("Error converting key %s: %v\n", keyStr, err) | |
| continue | |
| } | |
| ownMap[int32(key)] = value | |
| } | |
| for keyStr, value := range playerSet.Maps.TeamMap { | |
| key, err := strconv.Atoi(keyStr) | |
| if err != nil { | |
| fmt.Printf("Error converting key %s: %v\n", keyStr, err) | |
| continue | |
| } | |
| teamMap[int32(key)] = value | |
| } | |
| return salaryMap, projMap, ownMap, teamMap | |
| } | |
| func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U { | |
| result := make([]U, len(input)) | |
| for i, key := range input { | |
| if value, exists := valueMap[key]; exists { | |
| result[i] = value | |
| } else { | |
| var zero U | |
| result[i] = zero | |
| } | |
| } | |
| return result | |
| } | |
| func sortChars(strData string) string { | |
| runes := []rune(strData) | |
| slices.Sort(runes) | |
| return string(runes) | |
| } | |
| func rowMostCommon(row []int) (*int, *int) { | |
| if len(row) == 0 { | |
| return nil, nil | |
| } | |
| counts := make(map[int]int) | |
| for _, value := range row { | |
| counts[value]++ | |
| } | |
| if len(counts) < 2 { | |
| return nil, nil | |
| } | |
| mostCommon := 0 | |
| maxCount := 0 | |
| secondMost := 0 | |
| secondMax := 0 | |
| for value, count := range counts { | |
| if count > maxCount { | |
| secondMax = maxCount | |
| secondMost = mostCommon | |
| maxCount = count | |
| mostCommon = value | |
| } else if count > secondMax && count < maxCount { | |
| secondMax = count | |
| secondMost = value | |
| } | |
| } | |
| return &mostCommon, &secondMost | |
| } | |
| func rowBiggestAndSecond(row []int) (int, int) { | |
| if len(row) == 0 { | |
| return 0, 0 | |
| } | |
| counts := make(map[int]int) | |
| for _, value := range row { | |
| counts[value]++ | |
| } | |
| if len(counts) == 1 { | |
| return len(row), 0 | |
| } | |
| biggestVal := 0 | |
| secondBiggestVal := 0 | |
| for _, count := range counts { | |
| if count > biggestVal { | |
| secondBiggestVal = biggestVal | |
| biggestVal = count | |
| } else if count > secondBiggestVal && count < biggestVal { | |
| secondBiggestVal = count | |
| } | |
| } | |
| return biggestVal, secondBiggestVal | |
| } | |
| func createOverallDFs(players []Player, pos string) PlayerData { | |
| var filteredPlayers []Player | |
| for _, player := range players { | |
| if pos == "FLEX" { | |
| if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") { | |
| filteredPlayers = append(filteredPlayers, player) | |
| } | |
| } else { | |
| if strings.Contains(player.Position, pos) { | |
| filteredPlayers = append(filteredPlayers, player) | |
| } | |
| } | |
| } | |
| nameMap := make(map[int]string) | |
| for i, player := range filteredPlayers { | |
| nameMap[i] = player.Name | |
| } | |
| return PlayerData{ | |
| Players: filteredPlayers, | |
| NameMap: nameMap, | |
| } | |
| } | |
| func sumSalaryRows(data [][]int32) []int32 { | |
| result := make([]int32, len(data)) | |
| for i, row := range data { | |
| var sum int32 | |
| for _, value := range row { | |
| sum += value | |
| } | |
| result[i] = sum | |
| } | |
| return result | |
| } | |
| func sumOwnRows(data [][]float64) []float64 { | |
| result := make([]float64, len(data)) | |
| for i, row := range data { | |
| var sum float64 | |
| for _, value := range row { | |
| sum += value | |
| } | |
| result[i] = sum | |
| } | |
| return result | |
| } | |
| func sumProjRows(data [][]float64) []float64 { | |
| result := make([]float64, len(data)) | |
| for i, row := range data { | |
| var sum float64 | |
| for _, value := range row { | |
| sum += value | |
| } | |
| result[i] = sum | |
| } | |
| return result | |
| } | |
| func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int { | |
| var validIndicies []int | |
| for i, value := range values { | |
| if value <= maxVal { | |
| validIndicies = append(validIndicies, i) | |
| } | |
| } | |
| return validIndicies | |
| } | |
| func filterMin[T ~int32 | ~float64](values []T, minVal T) []int { | |
| var validIndicies []int | |
| for i, value := range values { | |
| if value >= minVal { | |
| validIndicies = append(validIndicies, i) | |
| } | |
| } | |
| return validIndicies | |
| } | |
| func sliceByIndicies[T any](data []T, indicies []int) []T { | |
| result := make([]T, len(indicies)) | |
| for i, idx := range indicies { | |
| result[i] = data[idx] | |
| } | |
| return result | |
| } | |
| func sortDataByField(data LineupData, field string, ascending bool) LineupData { | |
| indicies := make([]int, len(data.Ownership)) | |
| for i := range indicies { | |
| indicies[i] = i | |
| } | |
| switch field { | |
| case "salary": | |
| sort.Slice(indicies, func(i, j int) bool { | |
| if ascending { | |
| return data.Salary[indicies[i]] < data.Salary[indicies[j]] | |
| } | |
| return data.Salary[indicies[i]] > data.Salary[indicies[j]] | |
| }) | |
| case "projection": | |
| sort.Slice(indicies, func(i, j int) bool { | |
| if ascending { | |
| return data.Projection[indicies[i]] < data.Projection[indicies[j]] | |
| } | |
| return data.Projection[indicies[i]] > data.Projection[indicies[j]] | |
| }) | |
| case "ownership": | |
| sort.Slice(indicies, func(i, j int) bool { | |
| if ascending { | |
| return data.Ownership[indicies[i]] < data.Ownership[indicies[j]] | |
| } | |
| return data.Ownership[indicies[i]] > data.Ownership[indicies[j]] | |
| }) | |
| default: | |
| sort.Slice(indicies, func(i, j int) bool { | |
| return data.Projection[indicies[i]] > data.Projection[indicies[j]] | |
| }) | |
| } | |
| return LineupData{ | |
| Salary: sliceByIndicies(data.Salary, indicies), | |
| Projection: sliceByIndicies(data.Projection, indicies), | |
| Team: sliceByIndicies(data.Team, indicies), | |
| Team_count: sliceByIndicies(data.Team_count, indicies), | |
| Secondary: sliceByIndicies(data.Secondary, indicies), | |
| Secondary_count: sliceByIndicies(data.Secondary_count, indicies), | |
| Ownership: sliceByIndicies(data.Ownership, indicies), | |
| Players: sliceByIndicies(data.Players, indicies), | |
| } | |
| } | |
| func combineArrays(qb, rb1, rb2, wr1, wr2, wr3, te, flex, dst []int32) [][]int32 { | |
| length := len(qb) | |
| result := make([][]int32, length) | |
| for i := 0; i < length; i++ { | |
| result[i] = []int32{ | |
| qb[i], | |
| rb1[i], | |
| rb2[i], | |
| wr1[i], | |
| wr2[i], | |
| wr3[i], | |
| te[i], | |
| flex[i], | |
| dst[i], | |
| } | |
| } | |
| return result | |
| } | |
| func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData { | |
| salaries := make([][]int32, len(combinedArrays)) | |
| projections := make([][]float64, len(combinedArrays)) | |
| ownership := make([][]float64, len(combinedArrays)) | |
| teamArrays := make([][]string, len(combinedArrays)) | |
| for i, row := range combinedArrays { | |
| players := row[0:9] | |
| playerSalaries := processAndFill(players, salaryMap) | |
| playerProjections := processAndFill(players, projMap) | |
| playerOwnership := processAndFill(players, ownMap) | |
| playerTeams := processAndFill(players, teamMap) | |
| salaries[i] = playerSalaries | |
| projections[i] = playerProjections | |
| ownership[i] = playerOwnership | |
| teamArrays[i] = playerTeams | |
| } | |
| totalSalaries := sumSalaryRows(salaries) | |
| totalProjections := sumProjRows(projections) | |
| totalOwnership := sumOwnRows(ownership) | |
| teamData := make([]string, len(teamArrays)) | |
| teamCounts := make([]int32, len(teamArrays)) | |
| secondaryData := make([]string, len(teamArrays)) | |
| secondaryCounts := make([]int32, len(teamArrays)) | |
| for i, teams := range teamArrays { | |
| teamMap := make(map[string]int) | |
| uniqueTeams := make([]string, 0) | |
| for _, team := range teams { | |
| if _, exists := teamMap[team]; !exists { | |
| teamMap[team] = len(uniqueTeams) | |
| uniqueTeams = append(uniqueTeams, team) | |
| } | |
| } | |
| teamInts := make([]int, len(teams)) | |
| for j, team := range teams { | |
| teamInts[j] = teamMap[team] | |
| } | |
| mostCommon, secondMost := rowMostCommon(teamInts) | |
| if mostCommon != nil { | |
| teamData[i] = uniqueTeams[*mostCommon] | |
| teamCount, _ := rowBiggestAndSecond(teamInts) | |
| teamCounts[i] = int32(teamCount) | |
| } | |
| if secondMost != nil { | |
| secondaryData[i] = uniqueTeams[*secondMost] | |
| _, secondaryCount := rowBiggestAndSecond(teamInts) | |
| secondaryCounts[i] = int32(secondaryCount) | |
| } | |
| } | |
| var validIndicies []int | |
| if site == "DK" { | |
| validIndicies = filterMax(totalSalaries, int32(50000)) | |
| } else { | |
| validIndicies = filterMax(totalSalaries, int32(60000)) | |
| } | |
| validData := LineupData{ | |
| Salary: sliceByIndicies(totalSalaries, validIndicies), | |
| Projection: sliceByIndicies(totalProjections, validIndicies), | |
| Team: sliceByIndicies(teamData, validIndicies), | |
| Team_count: sliceByIndicies(teamCounts, validIndicies), | |
| Secondary: sliceByIndicies(secondaryData, validIndicies), | |
| Secondary_count: sliceByIndicies(secondaryCounts, validIndicies), | |
| Ownership: sliceByIndicies(totalOwnership, validIndicies), | |
| Players: sliceByIndicies(combinedArrays, validIndicies), | |
| } | |
| return sortDataByField(validData, "projection", false) | |
| } | |
| func calculateQuantile(values []float64, quantile float64) (float64, error) { | |
| if len(values) == 0 { | |
| return 0, fmt.Errorf("cannot calculate quantile of empty slice") | |
| } | |
| if quantile < 0 || quantile > 1 { | |
| return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile) | |
| } | |
| sorted := make([]float64, len(values)) | |
| copy(sorted, values) | |
| sort.Float64s(sorted) | |
| index := int(float64(len(sorted)-1) * quantile) | |
| return sorted[index], nil | |
| } | |
| func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) { | |
| if count > len(playerIDs) { | |
| return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs)) | |
| } | |
| shuffled := make([]int32, len(playerIDs)) | |
| copy(shuffled, playerIDs) | |
| for i := len(shuffled) - 1; i > 0; i-- { | |
| j := rng.Intn(i + 1) | |
| shuffled[i], shuffled[j] = shuffled[j], shuffled[i] | |
| } | |
| return shuffled[:count], nil | |
| } | |
| func generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) { | |
| // DEBUG: Check pool sizes | |
| fmt.Printf("DEBUG - Pool sizes: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d\n", | |
| len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers)) | |
| if len(qbPlayers) == 0 || len(rbPlayers) == 0 || len(wrPlayers) == 0 || len(tePlayers) == 0 || len(flexPlayers) == 0 || len(dstPlayers) == 0 { | |
| return nil, fmt.Errorf("one or more position pools is empty: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d", | |
| len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers)) | |
| } | |
| var validArrays [][]int32 | |
| attempts := 0 | |
| maxAttempts := numRows * 10 | |
| for len(validArrays) < numRows && attempts < maxAttempts { | |
| attempts++ | |
| qb := qbPlayers[rng.Intn(len(qbPlayers))] | |
| rb1 := rbPlayers[rng.Intn(len(rbPlayers))] | |
| rb2 := rbPlayers[rng.Intn(len(rbPlayers))] | |
| wr1 := wrPlayers[rng.Intn(len(wrPlayers))] | |
| wr2 := wrPlayers[rng.Intn(len(wrPlayers))] | |
| wr3 := wrPlayers[rng.Intn(len(wrPlayers))] | |
| te := tePlayers[rng.Intn(len(tePlayers))] | |
| flex := flexPlayers[rng.Intn(len(flexPlayers))] | |
| dst := dstPlayers[rng.Intn(len(dstPlayers))] | |
| if rb1.Name != rb2.Name && rb1.Name != flex.Name && rb2.Name != flex.Name && wr1.Name != wr2.Name && wr1.Name != wr3.Name && wr2.Name != wr3.Name && | |
| wr1.Name != flex.Name && wr2.Name != flex.Name && wr3.Name != flex.Name && te.Name != flex.Name { | |
| playerIDs := []int32{qb.ID, rb1.ID, rb2.ID, wr1.ID, wr2.ID, wr3.ID, te.ID, flex.ID, dst.ID} | |
| validArrays = append(validArrays, playerIDs) | |
| } | |
| } | |
| if len(validArrays) == 0 { | |
| return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows) | |
| } | |
| return validArrays, nil | |
| } | |
| func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) { | |
| if len(players) == 0 { | |
| return nil, fmt.Errorf("no players provided") | |
| } | |
| var filteredPlayers []Player | |
| for _, player := range players { | |
| if pos == "FLEX" { | |
| if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") { | |
| filteredPlayers = append(filteredPlayers, player) | |
| } | |
| } else { | |
| if strings.Contains(player.Position, pos) { | |
| filteredPlayers = append(filteredPlayers, player) | |
| } | |
| } | |
| } | |
| ownVals := make([]float64, len(filteredPlayers)) | |
| for i, player := range filteredPlayers { | |
| ownVals[i] = player.OwnValue | |
| } | |
| threshold, err := calculateQuantile(ownVals, strengthStep) | |
| if err != nil { | |
| return nil, err | |
| } | |
| var filtered []Player | |
| for _, player := range filteredPlayers { | |
| if player.OwnValue >= threshold { | |
| filtered = append(filtered, player) | |
| } | |
| } | |
| if len(filtered) == 0 { | |
| return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold) | |
| } | |
| return filtered, nil | |
| } | |
| func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) (LineupData, error) { | |
| qbPlayers, err := filterPosPlayersInQuantile(players, "QB", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter QB players: %v", err) | |
| } | |
| rbPlayers, err := filterPosPlayersInQuantile(players, "RB", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter RB players: %v", err) | |
| } | |
| wrPlayers, err := filterPosPlayersInQuantile(players, "WR", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter WR players: %v", err) | |
| } | |
| tePlayers, err := filterPosPlayersInQuantile(players, "TE", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter TE players: %v", err) | |
| } | |
| flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err) | |
| } | |
| dstPlayers, err := filterPosPlayersInQuantile(players, "DST", strengthStep) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to filter DST players: %v", err) | |
| } | |
| overallArrays, err := generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers, numRows, strengthStep, rng) | |
| if err != nil { | |
| return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err) | |
| } | |
| result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site) | |
| return result, nil | |
| } | |
| func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) ([]LineupData, error) { | |
| resultsChan := make(chan StrengthResult, len(strengthVars)) | |
| for i, strengthStep := range strengthVars { | |
| go func(step float64, rows int, index int) { | |
| rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index))) | |
| result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site) | |
| resultsChan <- StrengthResult{Index: index, Data: result, Error: err} | |
| if err != nil { | |
| fmt.Printf("Error in strength level %.2f: %v\n", step, err) | |
| } else { | |
| fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary)) | |
| } | |
| }(strengthStep, rowsPerLevel[i], i) | |
| } | |
| allResults := make([]LineupData, len(strengthVars)) | |
| var errors []error | |
| successCount := 0 | |
| for i := 0; i < len(strengthVars); i++ { | |
| result := <-resultsChan | |
| if result.Error != nil { | |
| errors = append(errors, result.Error) | |
| } else { | |
| allResults[result.Index] = result.Data | |
| successCount++ | |
| } | |
| } | |
| if successCount == 0 { | |
| return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors) | |
| } | |
| var validResults []LineupData | |
| for i, result := range allResults { | |
| if len(result.Salary) > 0 { | |
| validResults = append(validResults, result) | |
| } else { | |
| fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i]) | |
| } | |
| } | |
| fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars)) | |
| return validResults, nil | |
| } | |
| func printResults(results []LineupData, nameMap map[int32]string) { | |
| fmt.Printf("Generated %d strength levels:\n", len(results)) | |
| // Combine all results into one big dataset | |
| var allSalaries []int32 | |
| var allProjections []float64 | |
| var allOwnership []float64 | |
| var allPlayers [][]int32 | |
| for _, result := range results { | |
| allSalaries = append(allSalaries, result.Salary...) | |
| allProjections = append(allProjections, result.Projection...) | |
| allOwnership = append(allOwnership, result.Ownership...) | |
| allPlayers = append(allPlayers, result.Players...) | |
| } | |
| fmt.Printf("Total lineups generated: %d\n", len(allSalaries)) | |
| if len(allSalaries) > 0 { | |
| // Print top 5 lineups (highest projection) | |
| fmt.Printf("\nTop 5 lineups (by projection):\n") | |
| for i := 0; i < 5 && i < len(allSalaries); i++ { | |
| playerNames := []string{ | |
| getPlayerName(allPlayers[i][0], nameMap, "QB"), // QB | |
| getPlayerName(allPlayers[i][1], nameMap, "RB1"), // RB1 | |
| getPlayerName(allPlayers[i][2], nameMap, "RB2"), // RB2 | |
| getPlayerName(allPlayers[i][3], nameMap, "WR1"), // WR1 | |
| getPlayerName(allPlayers[i][4], nameMap, "WR2"), // WR2 | |
| getPlayerName(allPlayers[i][5], nameMap, "WR3"), // WR3 | |
| getPlayerName(allPlayers[i][6], nameMap, "TE"), // TE | |
| getPlayerName(allPlayers[i][7], nameMap, "FLEX"), // FLEX | |
| getPlayerName(allPlayers[i][8], nameMap, "DST"), // DST | |
| } | |
| fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n", | |
| i+1, allSalaries[i], allProjections[i], allOwnership[i]) | |
| fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n", | |
| playerNames[0], playerNames[1], playerNames[2], playerNames[3], | |
| playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8]) | |
| } | |
| // Print bottom 5 lineups (lowest projection) | |
| if len(allSalaries) > 5 { | |
| fmt.Printf("\nBottom 5 lineups (by projection):\n") | |
| start := len(allSalaries) - 5 | |
| for i := start; i < len(allSalaries); i++ { | |
| // Convert player IDs to names | |
| playerNames := []string{ | |
| getPlayerName(allPlayers[i][0], nameMap, "QB"), | |
| getPlayerName(allPlayers[i][1], nameMap, "RB1"), | |
| getPlayerName(allPlayers[i][2], nameMap, "RB2"), | |
| getPlayerName(allPlayers[i][3], nameMap, "WR1"), | |
| getPlayerName(allPlayers[i][4], nameMap, "WR2"), | |
| getPlayerName(allPlayers[i][5], nameMap, "WR3"), | |
| getPlayerName(allPlayers[i][6], nameMap, "TE"), | |
| getPlayerName(allPlayers[i][7], nameMap, "FLEX"), | |
| getPlayerName(allPlayers[i][8], nameMap, "DST"), | |
| } | |
| fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n", | |
| i+1, allSalaries[i], allProjections[i], allOwnership[i]) | |
| fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n", | |
| playerNames[0], playerNames[1], playerNames[2], playerNames[3], | |
| playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8]) | |
| } | |
| } | |
| } | |
| } | |
| func removeDuplicates(results []LineupData) []LineupData { | |
| seen := make(map[string]bool) | |
| var uniqueLineups []LineupData | |
| for _, result := range results { | |
| for i := 0; i < len(result.Players); i++ { | |
| // Create combo string like Python | |
| combo := fmt.Sprintf("%d%d%d%d%d%d%d%d%d", | |
| result.Players[i][0], result.Players[i][1], result.Players[i][2], | |
| result.Players[i][3], result.Players[i][4], result.Players[i][5], | |
| result.Players[i][6], result.Players[i][7], result.Players[i][8]) | |
| // Sort combo like Python | |
| sortedCombo := sortChars(combo) | |
| if !seen[sortedCombo] { | |
| seen[sortedCombo] = true | |
| uniqueLineups = append(uniqueLineups, LineupData{ | |
| Salary: []int32{result.Salary[i]}, | |
| Projection: []float64{result.Projection[i]}, | |
| Team: []string{result.Team[i]}, | |
| Team_count: []int32{result.Team_count[i]}, | |
| Secondary: []string{result.Secondary[i]}, | |
| Secondary_count: []int32{result.Secondary_count[i]}, | |
| Ownership: []float64{result.Ownership[i]}, | |
| Players: [][]int32{result.Players[i]}, | |
| }) | |
| } | |
| } | |
| } | |
| if len(uniqueLineups) == 0 { | |
| return []LineupData{} | |
| } | |
| var allSalary []int32 | |
| var allProjection []float64 | |
| var allTeam []string | |
| var allTeamCount []int32 | |
| var allSecondary []string | |
| var allSecondaryCount []int32 | |
| var allOwnership []float64 | |
| var allPlayers [][]int32 | |
| for _, lineup := range uniqueLineups { | |
| allSalary = append(allSalary, lineup.Salary[0]) | |
| allProjection = append(allProjection, lineup.Projection[0]) | |
| allTeam = append(allTeam, lineup.Team[0]) | |
| allTeamCount = append(allTeamCount, lineup.Team_count[0]) | |
| allSecondary = append(allSecondary, lineup.Secondary[0]) | |
| allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0]) | |
| allOwnership = append(allOwnership, lineup.Ownership[0]) | |
| allPlayers = append(allPlayers, lineup.Players[0]) | |
| } | |
| return []LineupData{{ | |
| Salary: allSalary, | |
| Projection: allProjection, | |
| Team: allTeam, | |
| Team_count: allTeamCount, | |
| Secondary: allSecondary, | |
| Secondary_count: allSecondaryCount, | |
| Ownership: allOwnership, | |
| Players: allPlayers, | |
| }} | |
| } | |
| func connectToMongoDB() (*mongo.Client, error) { | |
| uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority" | |
| clientOptions := options.Client(). | |
| ApplyURI(uri). | |
| SetRetryWrites(true). | |
| SetServerSelectionTimeout(10 * time.Second). | |
| SetMaxPoolSize(100). | |
| SetMinPoolSize(10). | |
| SetMaxConnIdleTime(30 * time.Second). | |
| SetRetryReads(true) | |
| client, err := mongo.Connect(context.TODO(), clientOptions) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to connect to MongoDB: %v", err) | |
| } | |
| err = client.Ping(context.TODO(), nil) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to ping mMongoDB %v", err) | |
| } | |
| fmt.Printf("Connected to MongoDB!") | |
| return client, nil | |
| } | |
| func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error { | |
| db := client.Database(fmt.Sprintf("%s_Database", sport)) | |
| collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here | |
| collection := db.Collection(collectionName) | |
| err := collection.Drop(context.TODO()) | |
| if err != nil { | |
| fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err) | |
| } | |
| var documents []interface{} | |
| for _, result := range results { | |
| if len(result.Salary) == 0 || len(result.Players) == 0 { | |
| fmt.Printf("Warning: Empty result found, skipping\n") | |
| continue | |
| } | |
| for i := 0; i < len(result.Salary); i++ { | |
| if len(result.Players[i]) < 9 { | |
| fmt.Printf("Warning: Lineup %d has only %d players, expected 9\n", i, len(result.Players[i])) | |
| } | |
| doc := LineupDocument{ | |
| Salary: result.Salary[i], | |
| Projection: result.Projection[i], | |
| Team: result.Team[i], | |
| Team_count: result.Team_count[i], | |
| Secondary: result.Secondary[i], | |
| Secondary_count: result.Secondary_count[i], | |
| Ownership: result.Ownership[i], | |
| QB: result.Players[i][0], | |
| RB1: result.Players[i][1], | |
| RB2: result.Players[i][2], | |
| WR1: result.Players[i][3], | |
| WR2: result.Players[i][4], | |
| WR3: result.Players[i][5], | |
| TE: result.Players[i][6], | |
| FLEX: result.Players[i][7], | |
| DST: result.Players[i][8], | |
| CreatedAt: time.Now(), | |
| } | |
| documents = append(documents, doc) | |
| } | |
| } | |
| if len(documents) == 0 { | |
| fmt.Printf("Warning: No documents to insert for slate %s\n", slate) | |
| } | |
| if len(documents) > 500000 { | |
| documents = documents[:500000] | |
| } | |
| chunkSize := 250000 | |
| for i := 0; i < len(documents); i += chunkSize { | |
| end := i + chunkSize | |
| if end > len(documents) { | |
| end = len(documents) | |
| } | |
| chunk := documents[i:end] | |
| for attempt := 0; attempt < 5; attempt++ { | |
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | |
| opts := options.InsertMany().SetOrdered(false) | |
| _, err := collection.InsertMany(ctx, chunk, opts) | |
| cancel() | |
| if err == nil { | |
| fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName) | |
| break | |
| } | |
| fmt.Printf("Retry %d due to error: %v\n", attempt+1, err) | |
| if attempt < 4 { | |
| time.Sleep(1 * time.Second) | |
| } | |
| } | |
| if err != nil { | |
| return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err) | |
| } | |
| } | |
| fmt.Printf("All documents inserted successfully to %s!\n", collectionName) | |
| return nil | |
| } | |
| func groupPlayersBySlate(players []Player) map[string][]Player { | |
| slateGroups := make(map[string][]Player) | |
| for _, player := range players { | |
| slateGroups[player.Slate] = append(slateGroups[player.Slate], player) | |
| } | |
| return slateGroups | |
| } | |
| func getPlayerName(playerID int32, nameMap map[int32]string, position string) string { | |
| if name, exists := nameMap[playerID]; exists && name != "" { | |
| return name | |
| } | |
| return fmt.Sprintf("Unknown_%s_%d", position, playerID) | |
| } | |
| func convertNamesToMaps(playerSet *PlayerSet) map[int32]string { | |
| nameMap := make(map[int32]string) | |
| for keyStr, value := range playerSet.Maps.NameMap { | |
| key, err := strconv.Atoi(keyStr) | |
| if err != nil { | |
| fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err) | |
| continue | |
| } | |
| nameMap[int32(key)] = value | |
| } | |
| return nameMap | |
| } | |
| func main() { | |
| site := "DK" | |
| sport := "NFL" | |
| if len(os.Args) > 1 { | |
| site = os.Args[1] | |
| } | |
| if len(os.Args) > 2 { | |
| sport = os.Args[2] | |
| } | |
| processedData, err := loadPlayerData() | |
| if err != nil { | |
| fmt.Printf("Error loading data: %v\n", err) | |
| return | |
| } | |
| start := time.Now() | |
| strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80} | |
| rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000} | |
| SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players) | |
| salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian) | |
| nameMap := convertNamesToMaps(&processedData.PlayersMedian) | |
| mongoClient, err := connectToMongoDB() | |
| if err != nil { | |
| fmt.Printf("Error connecting to MongoDB: %v\n", err) | |
| return | |
| } | |
| defer func() { | |
| if err := mongoClient.Disconnect(context.TODO()); err != nil { | |
| fmt.Printf("Error disconnecting from MongoDB: %v\n", err) | |
| } | |
| }() | |
| optimalsBySlate, err := loadOptimals() | |
| if err != nil { | |
| fmt.Printf("Warning: Could not load optimal lineups: %v\n", err) | |
| optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals | |
| } else { | |
| totalOptimals := 0 | |
| for _, optimals := range optimalsBySlate { | |
| totalOptimals += len(optimals.Salary) | |
| } | |
| fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals) | |
| } | |
| for slate, players := range SlateGroups { | |
| fmt.Printf("Processing slate: %s\n", slate) | |
| results, err := runSeedframeRoutines( | |
| players, strengthVars, rowsPerLevel, | |
| salaryMapJSON, projectionMapJSON, | |
| ownershipMapJSON, teamMapJSON, site) | |
| if err != nil { | |
| fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err) | |
| continue | |
| } | |
| // Get optimal lineups for this specific slate | |
| slateOptimals := optimalsBySlate[slate] | |
| // Append optimal lineups for this slate | |
| finalResults := appendOptimalLineups(results, slateOptimals) | |
| exportResults := removeDuplicates(finalResults) | |
| exportResults[0] = sortDataByField(exportResults[0], "projection", false) | |
| err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport) | |
| if err != nil { | |
| fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err) | |
| continue | |
| } | |
| printResults(exportResults, nameMap) | |
| } | |
| // Add this line at the end | |
| fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds()) | |
| } | |