Paydirt_model_updates / func /fd_nfl_go /NFL_seed_frames.go
James McCool
Adding NFL functionality
85eb84f
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())
}