James McCool commited on
Commit
4ad4542
Β·
1 Parent(s): 61b5f4b

adding NASCAR update functionality

Browse files
Dockerfile CHANGED
@@ -19,6 +19,7 @@ COPY func/fd_nfl_go ./func/fd_nfl_go
19
  COPY func/showdown_go ./func/showdown_go
20
  COPY func/pga_go ./func/pga_go
21
  COPY func/mma_go ./func/mma_go
 
22
 
23
  # Build the Go programs for Linux
24
  RUN CGO_ENABLED=0 GOOS=linux go build -o dk_nhl_seed ./func/dk_nhl_go/NHL_seed_frames.go
@@ -30,6 +31,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o fd_nfl_seed ./func/fd_nfl_go/NFL_seed_f
30
  RUN CGO_ENABLED=0 GOOS=linux go build -o showdown_seed ./func/showdown_go/showdown_seed_frames.go
31
  RUN CGO_ENABLED=0 GOOS=linux go build -o pga_seed ./func/pga_go/PGA_seed_frames.go
32
  RUN CGO_ENABLED=0 GOOS=linux go build -o mma_seed ./func/mma_go/MMA_seed_frames.go
 
33
 
34
  # Python stage
35
  FROM python:3.11-slim
@@ -60,6 +62,7 @@ COPY --from=go-builder /go-build/fd_nfl_seed ./fd_nfl_go/NFL_seed_frames
60
  COPY --from=go-builder /go-build/showdown_seed ./showdown_go/showdown_seed_frames
61
  COPY --from=go-builder /go-build/pga_seed ./pga_go/PGA_seed_frames
62
  COPY --from=go-builder /go-build/mma_seed ./mma_go/MMA_seed_frames
 
63
 
64
  # Make Go binaries executable
65
  RUN chmod +x ./dk_nhl_go/NHL_seed_frames ./fd_nhl_go/NHL_seed_frames
@@ -68,6 +71,7 @@ RUN chmod +x ./dk_nfl_go/NFL_seed_frames ./fd_nfl_go/NFL_seed_frames
68
  RUN chmod +x ./showdown_go/showdown_seed_frames
69
  RUN chmod +x ./pga_go/PGA_seed_frames
70
  RUN chmod +x ./mma_go/MMA_seed_frames
 
71
 
72
  # Create .streamlit directory for config
73
  RUN mkdir -p .streamlit
 
19
  COPY func/showdown_go ./func/showdown_go
20
  COPY func/pga_go ./func/pga_go
21
  COPY func/mma_go ./func/mma_go
22
+ COPY func/nascar_go ./func/nascar_go
23
 
24
  # Build the Go programs for Linux
25
  RUN CGO_ENABLED=0 GOOS=linux go build -o dk_nhl_seed ./func/dk_nhl_go/NHL_seed_frames.go
 
31
  RUN CGO_ENABLED=0 GOOS=linux go build -o showdown_seed ./func/showdown_go/showdown_seed_frames.go
32
  RUN CGO_ENABLED=0 GOOS=linux go build -o pga_seed ./func/pga_go/PGA_seed_frames.go
33
  RUN CGO_ENABLED=0 GOOS=linux go build -o mma_seed ./func/mma_go/MMA_seed_frames.go
34
+ RUN CGO_ENABLED=0 GOOS=linux go build -o nascar_seed ./func/nascar_go/NASCAR_seed_frames.go
35
 
36
  # Python stage
37
  FROM python:3.11-slim
 
62
  COPY --from=go-builder /go-build/showdown_seed ./showdown_go/showdown_seed_frames
63
  COPY --from=go-builder /go-build/pga_seed ./pga_go/PGA_seed_frames
64
  COPY --from=go-builder /go-build/mma_seed ./mma_go/MMA_seed_frames
65
+ COPY --from=go-builder /go-build/nascar_seed ./nascar_go/NASCAR_seed_frames
66
 
67
  # Make Go binaries executable
68
  RUN chmod +x ./dk_nhl_go/NHL_seed_frames ./fd_nhl_go/NHL_seed_frames
 
71
  RUN chmod +x ./showdown_go/showdown_seed_frames
72
  RUN chmod +x ./pga_go/PGA_seed_frames
73
  RUN chmod +x ./mma_go/MMA_seed_frames
74
+ RUN chmod +x ./nascar_go/NASCAR_seed_frames
75
 
76
  # Create .streamlit directory for config
77
  RUN mkdir -p .streamlit
func/nascar_go/NASCAR_seed_frames.go ADDED
@@ -0,0 +1,999 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ // Script Imports
5
+ "context"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io/ioutil"
9
+ "math/rand"
10
+ "os"
11
+ "slices"
12
+ "sort"
13
+ "strconv"
14
+ "strings"
15
+ "time"
16
+
17
+ // MongoDB Imports
18
+ "go.mongodb.org/mongo-driver/mongo"
19
+ "go.mongodb.org/mongo-driver/mongo/options"
20
+ )
21
+
22
+ type LineupData struct {
23
+ Salary []int32
24
+ Projection []float64
25
+ Ownership []float64
26
+ Players [][]int32
27
+ }
28
+
29
+ type Player struct {
30
+ ID int32 `json:"id"`
31
+ Name string `json:"name"`
32
+ Position string `json:"position"`
33
+ Salary int32 `json:"salary"`
34
+ Projection float64 `json:"projection"`
35
+ Ownership float64 `json:"ownership"`
36
+ SalaryValue float64 `json:"salary_value"`
37
+ ProjValue float64 `json:"proj_value"`
38
+ OwnValue float64 `json:"own_value"`
39
+ SortValue float64 `json:"sort_value"`
40
+ Slate string `json:"slate"`
41
+ }
42
+
43
+ type PlayerSet struct {
44
+ Players []Player `json:"players"`
45
+ Maps struct {
46
+ NameMap map[string]string `json:"name_map"`
47
+ SalaryMap map[string]int32 `json:"salary_map"`
48
+ ProjectionMap map[string]float64 `json:"projection_map"`
49
+ OwnershipMap map[string]float64 `json:"ownership_map"`
50
+ } `json:"maps"`
51
+ }
52
+
53
+ type ProcessedData struct {
54
+ PlayersMedian PlayerSet `json:"players_median"`
55
+ }
56
+
57
+ type PlayerData struct {
58
+ Players []Player
59
+ NameMap map[int]string
60
+ }
61
+
62
+ type StrengthResult struct {
63
+ Index int
64
+ Data LineupData
65
+ Error error
66
+ }
67
+
68
+ type LineupDocument struct {
69
+ Salary int32 `bson:"salary"`
70
+ Projection float64 `bson:"proj"`
71
+ Ownership float64 `bson:"Own"`
72
+ FLEX1 int32 `bson:"FLEX1"`
73
+ FLEX2 int32 `bson:"FLEX2"`
74
+ FLEX3 int32 `bson:"FLEX3"`
75
+ FLEX4 int32 `bson:"FLEX4"`
76
+ FLEX5 int32 `bson:"FLEX5"`
77
+ FLEX6 int32 `bson:"FLEX6"`
78
+ CreatedAt time.Time `bson:"created_at"`
79
+ }
80
+
81
+ func loadPlayerData() (*ProcessedData, error) {
82
+ data, err := ioutil.ReadFile("nascar_go/player_data.json")
83
+ if err != nil {
84
+ return nil, fmt.Errorf("failed to read in data: %v", err)
85
+ }
86
+
87
+ var processedData ProcessedData
88
+ if err := json.Unmarshal(data, &processedData); err != nil {
89
+ return nil, fmt.Errorf("failed to parse json: %v", err)
90
+ }
91
+
92
+ return &processedData, nil
93
+ }
94
+
95
+ func loadOptimals() (map[string]LineupData, error) {
96
+ data, err := ioutil.ReadFile("nascar_go/optimal_lineups.json")
97
+ if err != nil {
98
+ return nil, fmt.Errorf("failed to parse optimals: %v", err)
99
+ }
100
+
101
+ type OptimalsJSON struct {
102
+ Slate string `json:"slate"`
103
+ Salary int32 `json:"salary"`
104
+ Projection float64 `json:"projection"`
105
+ Ownership float64 `json:"ownership"`
106
+ Players []int32 `json:"players"`
107
+ }
108
+
109
+ var allOptimals []OptimalsJSON
110
+ if err := json.Unmarshal(data, &allOptimals); err != nil {
111
+ return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
112
+ }
113
+
114
+ optimalsBySlate := make(map[string]LineupData)
115
+
116
+ for _, optimal := range allOptimals {
117
+ if _, exists := optimalsBySlate[optimal.Slate]; !exists {
118
+ optimalsBySlate[optimal.Slate] = LineupData{
119
+ Salary: []int32{},
120
+ Projection: []float64{},
121
+ Ownership: []float64{},
122
+ Players: [][]int32{},
123
+ }
124
+ }
125
+
126
+ slateData := optimalsBySlate[optimal.Slate]
127
+ slateData.Salary = append(slateData.Salary, optimal.Salary)
128
+ slateData.Projection = append(slateData.Projection, optimal.Projection)
129
+ slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
130
+ slateData.Players = append(slateData.Players, optimal.Players)
131
+
132
+ optimalsBySlate[optimal.Slate] = slateData
133
+ }
134
+
135
+ return optimalsBySlate, nil
136
+ }
137
+
138
+ func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
139
+ if len(optimals.Salary) == 0 {
140
+ return results
141
+ }
142
+
143
+ // Simply append the optimal LineupData to existing results
144
+ return append(results, optimals)
145
+ }
146
+
147
+ func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64) {
148
+ salaryMap := make(map[int32]int32)
149
+ projMap := make(map[int32]float64)
150
+ ownMap := make(map[int32]float64)
151
+
152
+ for keyStr, value := range playerSet.Maps.SalaryMap {
153
+ key, err := strconv.Atoi(keyStr)
154
+ if err != nil {
155
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
156
+ continue
157
+ }
158
+ salaryMap[int32(key)] = value
159
+ }
160
+
161
+ for keyStr, value := range playerSet.Maps.ProjectionMap {
162
+ key, err := strconv.Atoi(keyStr)
163
+ if err != nil {
164
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
165
+ continue
166
+ }
167
+ projMap[int32(key)] = value
168
+ }
169
+
170
+ for keyStr, value := range playerSet.Maps.OwnershipMap {
171
+ key, err := strconv.Atoi(keyStr)
172
+ if err != nil {
173
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
174
+ continue
175
+ }
176
+ ownMap[int32(key)] = value
177
+ }
178
+
179
+ return salaryMap, projMap, ownMap
180
+ }
181
+
182
+ func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
183
+ result := make([]U, len(input))
184
+
185
+ for i, key := range input {
186
+ if value, exists := valueMap[key]; exists {
187
+ result[i] = value
188
+ } else {
189
+ var zero U
190
+ result[i] = zero
191
+ }
192
+ }
193
+
194
+ return result
195
+ }
196
+
197
+ func sortChars(strData string) string {
198
+ runes := []rune(strData)
199
+ slices.Sort(runes)
200
+ return string(runes)
201
+ }
202
+
203
+ func rowMostCommon(row []int) (*int, *int) {
204
+ if len(row) == 0 {
205
+ return nil, nil
206
+ }
207
+
208
+ counts := make(map[int]int)
209
+ for _, value := range row {
210
+ counts[value]++
211
+ }
212
+
213
+ if len(counts) < 2 {
214
+ return nil, nil
215
+ }
216
+
217
+ mostCommon := 0
218
+ maxCount := 0
219
+ secondMost := 0
220
+ secondMax := 0
221
+
222
+ for value, count := range counts {
223
+ if count > maxCount {
224
+ secondMax = maxCount
225
+ secondMost = mostCommon
226
+
227
+ maxCount = count
228
+ mostCommon = value
229
+ } else if count > secondMax && count < maxCount {
230
+ secondMax = count
231
+ secondMost = value
232
+ }
233
+
234
+ }
235
+
236
+ return &mostCommon, &secondMost
237
+ }
238
+
239
+ func rowBiggestAndSecond(row []int) (int, int) {
240
+ if len(row) == 0 {
241
+ return 0, 0
242
+ }
243
+
244
+ counts := make(map[int]int)
245
+ for _, value := range row {
246
+ counts[value]++
247
+ }
248
+
249
+ if len(counts) == 1 {
250
+ return len(row), 0
251
+ }
252
+
253
+ biggestVal := 0
254
+ secondBiggestVal := 0
255
+
256
+ for _, count := range counts {
257
+ if count > biggestVal {
258
+ secondBiggestVal = biggestVal
259
+ biggestVal = count
260
+ } else if count > secondBiggestVal && count < biggestVal {
261
+ secondBiggestVal = count
262
+ }
263
+ }
264
+
265
+ return biggestVal, secondBiggestVal
266
+ }
267
+
268
+ func createOverallDFs(players []Player, pos string) PlayerData {
269
+ var filteredPlayers []Player
270
+ filteredPlayers = append(filteredPlayers, players...)
271
+
272
+ nameMap := make(map[int]string)
273
+ for i, player := range filteredPlayers {
274
+ nameMap[i] = player.Name
275
+ }
276
+
277
+ return PlayerData{
278
+ Players: filteredPlayers,
279
+ NameMap: nameMap,
280
+ }
281
+ }
282
+
283
+ func sumSalaryRows(data [][]int32) []int32 {
284
+ result := make([]int32, len(data))
285
+
286
+ for i, row := range data {
287
+ var sum int32
288
+ for _, value := range row {
289
+ sum += value
290
+ }
291
+ result[i] = sum
292
+ }
293
+
294
+ return result
295
+ }
296
+
297
+ func sumOwnRows(data [][]float64) []float64 {
298
+ result := make([]float64, len(data))
299
+
300
+ for i, row := range data {
301
+ var sum float64
302
+ for _, value := range row {
303
+ sum += value
304
+ }
305
+ result[i] = sum
306
+ }
307
+
308
+ return result
309
+ }
310
+
311
+ func sumProjRows(data [][]float64) []float64 {
312
+ result := make([]float64, len(data))
313
+
314
+ for i, row := range data {
315
+ var sum float64
316
+ for _, value := range row {
317
+ sum += value
318
+ }
319
+ result[i] = sum
320
+ }
321
+
322
+ return result
323
+ }
324
+
325
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
326
+ var validIndicies []int
327
+
328
+ for i, value := range values {
329
+ if value <= maxVal {
330
+ validIndicies = append(validIndicies, i)
331
+ }
332
+ }
333
+
334
+ return validIndicies
335
+ }
336
+
337
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
338
+ var validIndicies []int
339
+
340
+ for i, value := range values {
341
+ if value >= minVal {
342
+ validIndicies = append(validIndicies, i)
343
+ }
344
+ }
345
+
346
+ return validIndicies
347
+ }
348
+
349
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
350
+ result := make([]T, len(indicies))
351
+
352
+ for i, idx := range indicies {
353
+ result[i] = data[idx]
354
+ }
355
+
356
+ return result
357
+ }
358
+
359
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
360
+ indicies := make([]int, len(data.Ownership))
361
+ for i := range indicies {
362
+ indicies[i] = i
363
+ }
364
+
365
+ switch field {
366
+ case "salary":
367
+ sort.Slice(indicies, func(i, j int) bool {
368
+ if ascending {
369
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
370
+ }
371
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
372
+ })
373
+ case "projection":
374
+ sort.Slice(indicies, func(i, j int) bool {
375
+ if ascending {
376
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
377
+ }
378
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
379
+ })
380
+ case "ownership":
381
+ sort.Slice(indicies, func(i, j int) bool {
382
+ if ascending {
383
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
384
+ }
385
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
386
+ })
387
+ default:
388
+ sort.Slice(indicies, func(i, j int) bool {
389
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
390
+ })
391
+ }
392
+
393
+ return LineupData{
394
+ Salary: sliceByIndicies(data.Salary, indicies),
395
+ Projection: sliceByIndicies(data.Projection, indicies),
396
+ Ownership: sliceByIndicies(data.Ownership, indicies),
397
+ Players: sliceByIndicies(data.Players, indicies),
398
+ }
399
+ }
400
+
401
+ func combineArrays(flex1, flex2, flex3, flex4, flex5, flex6 []int32) [][]int32 {
402
+ length := len(flex1)
403
+
404
+ result := make([][]int32, length)
405
+
406
+ for i := 0; i < length; i++ {
407
+ result[i] = []int32{
408
+ flex1[i],
409
+ flex2[i],
410
+ flex3[i],
411
+ flex4[i],
412
+ flex5[i],
413
+ flex6[i],
414
+ }
415
+ }
416
+
417
+ return result
418
+ }
419
+
420
+ func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, site string) LineupData {
421
+
422
+ salaries := make([][]int32, len(combinedArrays))
423
+ projections := make([][]float64, len(combinedArrays))
424
+ ownership := make([][]float64, len(combinedArrays))
425
+
426
+ for i, row := range combinedArrays {
427
+
428
+ players := row[0:6]
429
+
430
+ playerSalaries := processAndFill(players, salaryMap)
431
+ playerProjections := processAndFill(players, projMap)
432
+ playerOwnership := processAndFill(players, ownMap)
433
+
434
+ salaries[i] = playerSalaries
435
+ projections[i] = playerProjections
436
+ ownership[i] = playerOwnership
437
+ }
438
+
439
+ totalSalaries := sumSalaryRows(salaries)
440
+ totalProjections := sumProjRows(projections)
441
+ totalOwnership := sumOwnRows(ownership)
442
+
443
+ var validIndicies []int
444
+ if site == "DK" {
445
+ validIndicies = filterMax(totalSalaries, int32(50000))
446
+ } else {
447
+ validIndicies = filterMax(totalSalaries, int32(100))
448
+ }
449
+
450
+ validData := LineupData{
451
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
452
+ Projection: sliceByIndicies(totalProjections, validIndicies),
453
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
454
+ Players: sliceByIndicies(combinedArrays, validIndicies),
455
+ }
456
+
457
+ return sortDataByField(validData, "projection", false)
458
+ }
459
+
460
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
461
+ if len(values) == 0 {
462
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
463
+ }
464
+
465
+ if quantile < 0 || quantile > 1 {
466
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
467
+ }
468
+
469
+ sorted := make([]float64, len(values))
470
+ copy(sorted, values)
471
+ sort.Float64s(sorted)
472
+
473
+ index := int(float64(len(sorted)-1) * quantile)
474
+ return sorted[index], nil
475
+ }
476
+
477
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
478
+ if count > len(playerIDs) {
479
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
480
+ }
481
+
482
+ shuffled := make([]int32, len(playerIDs))
483
+ copy(shuffled, playerIDs)
484
+
485
+ for i := len(shuffled) - 1; i > 0; i-- {
486
+ j := rng.Intn(i + 1)
487
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
488
+ }
489
+
490
+ return shuffled[:count], nil
491
+ }
492
+
493
+ func generateBaseArrays(flexPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
494
+
495
+ // DEBUG: Check pool sizes
496
+ fmt.Printf("DEBUG - Pool sizes: FLEX=%d\n", len(flexPlayers))
497
+
498
+ if len(flexPlayers) == 0 {
499
+ return nil, fmt.Errorf("one or more position pools is empty: FLEX=%d", len(flexPlayers))
500
+ }
501
+
502
+ var validArrays [][]int32
503
+ attempts := 0
504
+ maxAttempts := numRows * 10
505
+
506
+ for len(validArrays) < numRows && attempts < maxAttempts {
507
+ attempts++
508
+
509
+ flex1 := flexPlayers[rng.Intn(len(flexPlayers))]
510
+ flex2 := flexPlayers[rng.Intn(len(flexPlayers))]
511
+ flex3 := flexPlayers[rng.Intn(len(flexPlayers))]
512
+ flex4 := flexPlayers[rng.Intn(len(flexPlayers))]
513
+ flex5 := flexPlayers[rng.Intn(len(flexPlayers))]
514
+ flex6 := flexPlayers[rng.Intn(len(flexPlayers))]
515
+
516
+ if flex1.Name != flex2.Name && flex1.Name != flex3.Name && flex1.Name != flex4.Name && flex1.Name != flex5.Name && flex1.Name != flex6.Name &&
517
+ flex2.Name != flex3.Name && flex2.Name != flex4.Name && flex2.Name != flex5.Name && flex2.Name != flex6.Name &&
518
+ flex3.Name != flex4.Name && flex3.Name != flex5.Name && flex3.Name != flex6.Name && flex4.Name != flex5.Name && flex4.Name != flex6.Name && flex5.Name != flex6.Name {
519
+
520
+ playerIDs := []int32{flex1.ID, flex2.ID, flex3.ID, flex4.ID, flex5.ID, flex6.ID}
521
+ validArrays = append(validArrays, playerIDs)
522
+ }
523
+ }
524
+
525
+ if len(validArrays) == 0 {
526
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
527
+ }
528
+
529
+ return validArrays, nil
530
+ }
531
+
532
+ func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
533
+ if len(players) == 0 {
534
+ return nil, fmt.Errorf("no players provided")
535
+ }
536
+
537
+ var filteredPlayers []Player
538
+ filteredPlayers = append(filteredPlayers, players...)
539
+
540
+ ownVals := make([]float64, len(filteredPlayers))
541
+ for i, player := range filteredPlayers {
542
+ ownVals[i] = player.OwnValue
543
+ }
544
+
545
+ threshold, err := calculateQuantile(ownVals, strengthStep)
546
+ if err != nil {
547
+ return nil, err
548
+ }
549
+
550
+ var filtered []Player
551
+ for _, player := range filteredPlayers {
552
+ if player.OwnValue >= threshold {
553
+ filtered = append(filtered, player)
554
+ }
555
+ }
556
+
557
+ if len(filtered) == 0 {
558
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
559
+ }
560
+
561
+ return filtered, nil
562
+ }
563
+
564
+ func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, site string) (LineupData, error) {
565
+
566
+ flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep)
567
+ if err != nil {
568
+ return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err)
569
+ }
570
+
571
+ overallArrays, err := generateBaseArrays(flexPlayers, numRows, strengthStep, rng)
572
+ if err != nil {
573
+ return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
574
+ }
575
+
576
+ result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, site)
577
+
578
+ return result, nil
579
+ }
580
+
581
+ func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, site string) ([]LineupData, error) {
582
+ resultsChan := make(chan StrengthResult, len(strengthVars))
583
+
584
+ for i, strengthStep := range strengthVars {
585
+ go func(step float64, rows int, index int) {
586
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
587
+
588
+ result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, site)
589
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
590
+
591
+ if err != nil {
592
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
593
+ } else {
594
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
595
+ }
596
+ }(strengthStep, rowsPerLevel[i], i)
597
+ }
598
+
599
+ allResults := make([]LineupData, len(strengthVars))
600
+ var errors []error
601
+ successCount := 0
602
+
603
+ for i := 0; i < len(strengthVars); i++ {
604
+ result := <-resultsChan
605
+ if result.Error != nil {
606
+ errors = append(errors, result.Error)
607
+ } else {
608
+ allResults[result.Index] = result.Data
609
+ successCount++
610
+ }
611
+ }
612
+
613
+ if successCount == 0 {
614
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
615
+ }
616
+
617
+ var validResults []LineupData
618
+ for i, result := range allResults {
619
+ if len(result.Salary) > 0 {
620
+ validResults = append(validResults, result)
621
+ } else {
622
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
623
+ }
624
+ }
625
+
626
+ fmt.Printf("Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
627
+ return validResults, nil
628
+ }
629
+
630
+ func printResults(results []LineupData, nameMap map[int32]string) {
631
+ fmt.Printf("Generated %d strength levels:\n", len(results))
632
+
633
+ // Combine all results into one big dataset
634
+ var allSalaries []int32
635
+ var allProjections []float64
636
+ var allOwnership []float64
637
+ var allPlayers [][]int32
638
+
639
+ for _, result := range results {
640
+ allSalaries = append(allSalaries, result.Salary...)
641
+ allProjections = append(allProjections, result.Projection...)
642
+ allOwnership = append(allOwnership, result.Ownership...)
643
+ allPlayers = append(allPlayers, result.Players...)
644
+ }
645
+
646
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
647
+
648
+ if len(allSalaries) > 0 {
649
+ // Print top 5 lineups (highest projection)
650
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
651
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
652
+ playerNames := []string{
653
+ getPlayerName(allPlayers[i][0], nameMap, "FLEX1"), // QB
654
+ getPlayerName(allPlayers[i][1], nameMap, "FLEX2"), // RB1
655
+ getPlayerName(allPlayers[i][2], nameMap, "FLEX3"), // RB2
656
+ getPlayerName(allPlayers[i][3], nameMap, "FLEX4"), // WR1
657
+ getPlayerName(allPlayers[i][4], nameMap, "FLEX5"), // WR2
658
+ getPlayerName(allPlayers[i][5], nameMap, "FLEX6"), // WR3
659
+ }
660
+
661
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
662
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
663
+ fmt.Printf(" Players: FLEX1=%s, FLEX2=%s, FLEX3=%s, FLEX4=%s, FLEX5=%s, FLEX6=%s\n",
664
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
665
+ playerNames[4], playerNames[5])
666
+ }
667
+
668
+ // Print bottom 5 lineups (lowest projection)
669
+ if len(allSalaries) > 5 {
670
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
671
+ start := len(allSalaries) - 5
672
+ for i := start; i < len(allSalaries); i++ {
673
+ // Convert player IDs to names
674
+ playerNames := []string{
675
+ getPlayerName(allPlayers[i][0], nameMap, "FLEX1"),
676
+ getPlayerName(allPlayers[i][1], nameMap, "FLEX2"),
677
+ getPlayerName(allPlayers[i][2], nameMap, "FLEX3"),
678
+ getPlayerName(allPlayers[i][3], nameMap, "FLEX4"),
679
+ getPlayerName(allPlayers[i][4], nameMap, "FLEX5"),
680
+ getPlayerName(allPlayers[i][5], nameMap, "FLEX6"),
681
+ }
682
+
683
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
684
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
685
+ fmt.Printf(" Players: FLEX1=%s, FLEX2=%s, FLEX3=%s, FLEX4=%s, FLEX5=%s, FLEX6=%s\n",
686
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
687
+ playerNames[4], playerNames[5])
688
+ }
689
+ }
690
+ }
691
+ }
692
+
693
+ func removeDuplicates(results []LineupData) []LineupData {
694
+ seen := make(map[string]bool)
695
+ var uniqueLineups []LineupData
696
+
697
+ for _, result := range results {
698
+ for i := 0; i < len(result.Players); i++ {
699
+ // Create combo string like Python
700
+ combo := fmt.Sprintf("%d%d%d%d%d%d",
701
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
702
+ result.Players[i][3], result.Players[i][4], result.Players[i][5])
703
+
704
+ // Sort combo like Python
705
+ sortedCombo := sortChars(combo)
706
+
707
+ if !seen[sortedCombo] {
708
+ seen[sortedCombo] = true
709
+ uniqueLineups = append(uniqueLineups, LineupData{
710
+ Salary: []int32{result.Salary[i]},
711
+ Projection: []float64{result.Projection[i]},
712
+ Ownership: []float64{result.Ownership[i]},
713
+ Players: [][]int32{result.Players[i]},
714
+ })
715
+ }
716
+ }
717
+ }
718
+
719
+ if len(uniqueLineups) == 0 {
720
+ return []LineupData{}
721
+ }
722
+
723
+ var allSalary []int32
724
+ var allProjection []float64
725
+ var allOwnership []float64
726
+ var allPlayers [][]int32
727
+
728
+ for _, lineup := range uniqueLineups {
729
+ allSalary = append(allSalary, lineup.Salary[0])
730
+ allProjection = append(allProjection, lineup.Projection[0])
731
+ allOwnership = append(allOwnership, lineup.Ownership[0])
732
+ allPlayers = append(allPlayers, lineup.Players[0])
733
+ }
734
+
735
+ return []LineupData{{
736
+ Salary: allSalary,
737
+ Projection: allProjection,
738
+ Ownership: allOwnership,
739
+ Players: allPlayers,
740
+ }}
741
+ }
742
+
743
+ func connectToMongoDB() (*mongo.Client, error) {
744
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
745
+
746
+ clientOptions := options.Client().
747
+ ApplyURI(uri).
748
+ SetRetryWrites(true).
749
+ SetServerSelectionTimeout(15 * time.Second).
750
+ SetMaxPoolSize(20).
751
+ SetMinPoolSize(2).
752
+ SetMaxConnIdleTime(45 * time.Second).
753
+ SetRetryReads(true)
754
+
755
+ client, err := mongo.Connect(context.TODO(), clientOptions)
756
+ if err != nil {
757
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
758
+ }
759
+
760
+ err = client.Ping(context.TODO(), nil)
761
+ if err != nil {
762
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
763
+ }
764
+
765
+ fmt.Printf("Connected to MongoDB!")
766
+ return client, nil
767
+ }
768
+
769
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string, contestType string) error {
770
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
771
+
772
+ collectionName := fmt.Sprintf("%s_%s_%s_seed_frame_%s", site, sport, contestType, slate) // NOTE: change the database here
773
+ collection := db.Collection(collectionName)
774
+
775
+ err := collection.Drop(context.TODO())
776
+ if err != nil {
777
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
778
+ }
779
+
780
+ var documents []interface{}
781
+
782
+ for _, result := range results {
783
+
784
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
785
+ fmt.Printf("Warning: Empty result found, skipping\n")
786
+ continue
787
+ }
788
+
789
+ for i := 0; i < len(result.Salary); i++ {
790
+ if len(result.Players[i]) < 6 {
791
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 6\n", i, len(result.Players[i]))
792
+ }
793
+
794
+ doc := LineupDocument{
795
+ Salary: result.Salary[i],
796
+ Projection: result.Projection[i],
797
+ Ownership: result.Ownership[i],
798
+ FLEX1: result.Players[i][0],
799
+ FLEX2: result.Players[i][1],
800
+ FLEX3: result.Players[i][2],
801
+ FLEX4: result.Players[i][3],
802
+ FLEX5: result.Players[i][4],
803
+ FLEX6: result.Players[i][5],
804
+ CreatedAt: time.Now(),
805
+ }
806
+
807
+ documents = append(documents, doc)
808
+ }
809
+ }
810
+
811
+ if len(documents) == 0 {
812
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
813
+ }
814
+
815
+ if len(documents) > 500000 {
816
+ documents = documents[:500000]
817
+ }
818
+
819
+ // Adaptive chunk sizing
820
+ baseChunkSize := 100000
821
+ currentChunkSize := baseChunkSize
822
+ i := 0
823
+
824
+ for i < len(documents) {
825
+ end := i + currentChunkSize
826
+ if end > len(documents) {
827
+ end = len(documents)
828
+ }
829
+
830
+ chunk := documents[i:end]
831
+
832
+ ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
833
+ opts := options.InsertMany().SetOrdered(false)
834
+ _, insertErr := collection.InsertMany(ctx, chunk, opts)
835
+ cancel()
836
+
837
+ if insertErr == nil {
838
+ // Success - advance position and reset chunk size to base
839
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
840
+ i = end
841
+ currentChunkSize = baseChunkSize
842
+ continue
843
+ }
844
+
845
+ // Check if it's a retryable network error
846
+ errStr := insertErr.Error()
847
+ if !strings.Contains(errStr, "i/o timeout") &&
848
+ !strings.Contains(errStr, "connection reset") &&
849
+ !strings.Contains(errStr, "unable to write wire message") &&
850
+ !strings.Contains(errStr, "incomplete read") {
851
+ return fmt.Errorf("error inserting chunk %d-%d: %v", i, end, insertErr)
852
+ }
853
+
854
+ fmt.Printf("WARNING: Chunk %d-%d failed with size %d: %v\n", i, end, currentChunkSize, insertErr)
855
+
856
+ // Reduce chunk size or fail if already at minimum
857
+ if currentChunkSize > 25000 {
858
+ if currentChunkSize == 100000 {
859
+ currentChunkSize = 50000
860
+ } else {
861
+ currentChunkSize = 25000
862
+ }
863
+ fmt.Printf("Reducing chunk size to %d and retrying...\n", currentChunkSize)
864
+
865
+ // Refresh connection before retry
866
+ pingCtx, pingCancel := context.WithTimeout(context.Background(), 5*time.Second)
867
+ client.Ping(pingCtx, nil)
868
+ pingCancel()
869
+
870
+ time.Sleep(2 * time.Second)
871
+ } else {
872
+ return fmt.Errorf("error inserting chunk %d-%d after reducing to minimum chunk size (25000): %v", i, end, insertErr)
873
+ }
874
+ }
875
+
876
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
877
+ return nil
878
+ }
879
+
880
+ func groupPlayersBySlate(players []Player) map[string][]Player {
881
+ slateGroups := make(map[string][]Player)
882
+
883
+ for _, player := range players {
884
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
885
+ }
886
+
887
+ return slateGroups
888
+ }
889
+
890
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
891
+ if name, exists := nameMap[playerID]; exists && name != "" {
892
+ return name
893
+ }
894
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
895
+ }
896
+
897
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
898
+ nameMap := make(map[int32]string)
899
+
900
+ for keyStr, value := range playerSet.Maps.NameMap {
901
+ key, err := strconv.Atoi(keyStr)
902
+ if err != nil {
903
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
904
+ continue
905
+ }
906
+ nameMap[int32(key)] = value
907
+ }
908
+
909
+ return nameMap
910
+ }
911
+
912
+ func main() {
913
+ site := "DK"
914
+ sport := "NASCAR"
915
+ contestType := "Classic"
916
+ if len(os.Args) > 1 {
917
+ site = os.Args[1]
918
+ }
919
+ if len(os.Args) > 2 {
920
+ sport = os.Args[2]
921
+ }
922
+ if len(os.Args) > 3 {
923
+ contestType = os.Args[3]
924
+ }
925
+ processedData, err := loadPlayerData()
926
+ if err != nil {
927
+ fmt.Printf("Error loading data: %v\n", err)
928
+ return
929
+ }
930
+
931
+ start := time.Now()
932
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
933
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
934
+
935
+ SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
936
+
937
+ salaryMapJSON, projectionMapJSON, ownershipMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
938
+
939
+ nameMap := convertNamesToMaps(&processedData.PlayersMedian)
940
+
941
+ mongoClient, err := connectToMongoDB()
942
+ if err != nil {
943
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
944
+ return
945
+ }
946
+ defer func() {
947
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
948
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
949
+ }
950
+ }()
951
+
952
+ optimalsBySlate, err := loadOptimals()
953
+ if err != nil {
954
+ fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
955
+ optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
956
+ } else {
957
+ totalOptimals := 0
958
+ for _, optimals := range optimalsBySlate {
959
+ totalOptimals += len(optimals.Salary)
960
+ }
961
+ fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
962
+ }
963
+
964
+ for slate, players := range SlateGroups {
965
+
966
+ fmt.Printf("Processing slate: %s\n", slate)
967
+
968
+ results, err := runSeedframeRoutines(
969
+ players, strengthVars, rowsPerLevel,
970
+ salaryMapJSON, projectionMapJSON,
971
+ ownershipMapJSON, site)
972
+
973
+ if err != nil {
974
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
975
+ continue
976
+ }
977
+
978
+ // Get optimal lineups for this specific slate
979
+ slateOptimals := optimalsBySlate[slate]
980
+
981
+ // Append optimal lineups for this slate
982
+ finalResults := appendOptimalLineups(results, slateOptimals)
983
+
984
+ exportResults := removeDuplicates(finalResults)
985
+
986
+ exportResults[0] = sortDataByField(exportResults[0], "projection", false)
987
+
988
+ err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport, contestType)
989
+ if err != nil {
990
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
991
+ continue
992
+ }
993
+
994
+ printResults(exportResults, nameMap)
995
+ }
996
+
997
+ // Add this line at the end
998
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
999
+ }
src/database.py CHANGED
@@ -56,6 +56,7 @@ nfl_db = client['NFL_Database']
56
  nba_db = client['NBA_Database']
57
  pga_db = client['PGA_Database']
58
  mma_db = client['MMA_Database']
 
59
  contest_db = client["Contest_Information"]
60
 
61
  # Google Sheets URLs
@@ -64,6 +65,7 @@ NBA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-
64
  NFL_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1I_1Ve3F4tftgfLQQoRKOJ351XfEG48s36OxXUKxmgS8/edit#gid=1668460741'
65
  PGA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1lMLxWdvCnOFBtG9dhM0zv2USuxZbkogI_2jnxFfQVVs/edit?gid=1596772515#gid=1596772515'
66
  MMA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Q1mnICesMHT0xgd4-ZOqYOUOXy2GiaOnrm5T5Jh7Doo/edit?gid=870330026#gid=870330026'
 
67
 
68
  # Discord Webhook
69
  discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
 
56
  nba_db = client['NBA_Database']
57
  pga_db = client['PGA_Database']
58
  mma_db = client['MMA_Database']
59
+ nascar_db = client['NASCAR_Database']
60
  contest_db = client["Contest_Information"]
61
 
62
  # Google Sheets URLs
 
65
  NFL_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1I_1Ve3F4tftgfLQQoRKOJ351XfEG48s36OxXUKxmgS8/edit#gid=1668460741'
66
  PGA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1lMLxWdvCnOFBtG9dhM0zv2USuxZbkogI_2jnxFfQVVs/edit?gid=1596772515#gid=1596772515'
67
  MMA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Q1mnICesMHT0xgd4-ZOqYOUOXy2GiaOnrm5T5Jh7Doo/edit?gid=870330026#gid=870330026'
68
+ NASCAR_Master_Hold: str = 'https://docs.google.com/spreadsheets/d/1fajiX2E2gQV2vAjWVhPl7kMc2fmNUtMuqTCY06c4HKY/edit?gid=65381622#gid=65381622'
69
 
70
  # Discord Webhook
71
  discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
src/sports/nascar_functions.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Numpy
2
+ from numpy import random as np_random
3
+ from numpy import array as np_array
4
+ from numpy import zeros as np_zeros
5
+ from numpy import nan as np_nan
6
+
7
+ # Pandas
8
+ from pandas import DataFrame
9
+ from pandas import merge as pd_merge
10
+ from pandas import concat as pd_concat
11
+ from pandas import Series as pd_Series
12
+ from pandas import options as pd_options
13
+ from pandas import errors as pd_errors
14
+ from pandas import options as poptions
15
+
16
+ # Time
17
+ import time
18
+ from time import sleep as time_sleep
19
+ from pytz import timezone as tz
20
+ from datetime import datetime
21
+
22
+ # Database
23
+ from pymongo import MongoClient
24
+
25
+ # Misc
26
+ from ortools.linear_solver import pywraplp
27
+ from random import choice
28
+ pd_options.mode.chained_assignment = None # default='warn'
29
+ from warnings import simplefilter
30
+ simplefilter(action="ignore", category=pd_errors.PerformanceWarning)
31
+ poptions.mode.chained_assignment = None # default='warn'
32
+
33
+ # GO
34
+ import json
35
+ import subprocess
36
+ import os
37
+
38
+ def run_go_classic_lineup_generator(site="DK", sport="NASCAR", contestType="Classic"):
39
+ """Run the Go lineup generator after Python data processing"""
40
+ try:
41
+ print(f"Starting Go {sport} lineup generation...")
42
+ start_time = time.time()
43
+
44
+ # Run the Go executable
45
+ result = subprocess.run(
46
+ ["./nascar_go/NASCAR_seed_frames.exe", site, sport, contestType], # Windows
47
+ capture_output=True,
48
+ text=True,
49
+ check=True,
50
+ cwd="." # Make sure we're in the right directory
51
+ )
52
+
53
+ end_time = time.time()
54
+ print(f"Go {sport} processing completed in {end_time - start_time:.2f} seconds")
55
+
56
+ if result.stdout:
57
+ print("Go output:", result.stdout)
58
+ if result.stderr:
59
+ print("Go warnings:", result.stderr)
60
+
61
+ return True
62
+
63
+ except subprocess.CalledProcessError as e:
64
+ print(f"Go process failed with exit code {e.returncode}")
65
+ if e.stdout:
66
+ print("Stdout:", e.stdout)
67
+ if e.stderr:
68
+ print("Error output:", e.stderr)
69
+ return False
70
+ except FileNotFoundError:
71
+ print("Error: NASCAR_seed_frames.exe not found. Make sure it's compiled and in the current directory.")
72
+ return False
73
+
74
+ def init_nascar_optimals(model_source: DataFrame, salary_cap: int, combos: int):
75
+ """
76
+ Generate optimal NASCAR lineups.
77
+
78
+ NASCAR Rules:
79
+ - 6 FLEX positions (FLEX1-FLEX6)
80
+ - A player cannot be in the same lineup as their opponent (Opp)
81
+ - Salary cap constraint
82
+ """
83
+ df = model_source.copy()
84
+
85
+ # Create solver
86
+ solver = pywraplp.Solver.CreateSolver('SCIP')
87
+ if not solver:
88
+ raise Exception("Could not create solver.")
89
+
90
+ # Variables: x[i] = 1 if player i is selected
91
+ x = {}
92
+ for i in df.index:
93
+ x[i] = solver.BoolVar(f'x_{i}')
94
+
95
+ # Constraint: Select exactly 6 players
96
+ solver.Add(solver.Sum([x[i] for i in df.index]) == 6)
97
+
98
+ # Constraint: Salary cap
99
+ solver.Add(
100
+ solver.Sum(x[i] * df.loc[i, 'Salary'] for i in df.index) <= salary_cap
101
+ )
102
+
103
+ # Objective: maximize total median
104
+ objective = solver.Sum(x[i] * df.loc[i, 'Median'] for i in df.index)
105
+ solver.Maximize(objective)
106
+
107
+ status = solver.Solve()
108
+
109
+ if status != pywraplp.Solver.OPTIMAL and status != pywraplp.Solver.FEASIBLE:
110
+ return []
111
+
112
+ # Get selected players
113
+ selected_indices = [i for i in df.index if x[i].solution_value() > 0.5]
114
+ lineup = df.loc[selected_indices].copy()
115
+
116
+ # Assign FLEX1-FLEX6 labels
117
+ lineup = lineup.sort_values(by='Median', ascending=False).reset_index(drop=True)
118
+ lineup['position_label'] = [f'FLEX{i+1}' for i in range(len(lineup))]
119
+
120
+ # Build the row dictionary
121
+ row_dict = {row['position_label']: row['Player'] for _, row in lineup.iterrows()}
122
+ row_dict['Total_Salary'] = lineup['Salary'].sum()
123
+ row_dict['Total_Median'] = lineup['Median'].sum()
124
+ row_dict['Own'] = lineup['Own'].sum()
125
+
126
+ # Generate variations
127
+ result_rows = [row_dict.copy()]
128
+ pos_labels = ['FLEX1', 'FLEX2', 'FLEX3', 'FLEX4', 'FLEX5', 'FLEX6']
129
+
130
+ for _ in range(combos):
131
+ new_row = row_dict.copy()
132
+ swapped_positions = []
133
+
134
+ # Determine number of positions to swap (1-3)
135
+ num_swaps = choice([1, 2, 3])
136
+
137
+ for _ in range(num_swaps):
138
+ available_positions = [pos for pos in pos_labels if pos not in swapped_positions]
139
+ if not available_positions:
140
+ break
141
+
142
+ pos_to_swap = choice(available_positions)
143
+ swapped_positions.append(pos_to_swap)
144
+
145
+ # Get current lineup players
146
+ current_players = [new_row[p] for p in pos_labels]
147
+
148
+ # Find eligible replacements:
149
+ # - Not already in lineup
150
+ # - Not an opponent of anyone in lineup
151
+ eligible = df[
152
+ (~df['Player'].isin(current_players))
153
+ ]
154
+
155
+ if eligible.empty:
156
+ continue
157
+
158
+ # Randomly select a replacement
159
+ replacement = eligible.sample(1).iloc[0]
160
+ new_row[pos_to_swap] = replacement['Player']
161
+
162
+ # Recalculate totals
163
+ player_rows = df[df['Player'].isin([new_row[k] for k in pos_labels])]
164
+ new_row['Total_Salary'] = player_rows['Salary'].sum()
165
+ new_row['Total_Median'] = player_rows['Median'].sum()
166
+ new_row['Own'] = player_rows['Own'].sum()
167
+
168
+ result_rows.append(new_row)
169
+
170
+ # Create final DataFrame
171
+ final_df = DataFrame(result_rows)
172
+ final_df = final_df.drop_duplicates(subset=['Total_Median', 'Total_Salary'])
173
+ final_df = final_df[final_df['Total_Salary'] <= salary_cap]
174
+ final_df = final_df.sort_values(by='Total_Median', ascending=False)
175
+
176
+ return [final_df]
177
+
178
+ def format_nascar_optimals(all_results: list, model_source: DataFrame):
179
+ """
180
+ Format NASCAR optimal lineups for output.
181
+
182
+ Args:
183
+ all_results: List of DataFrames with optimal lineups
184
+ model_source: Original player data
185
+
186
+ Returns:
187
+ Formatted DataFrame with lineup data
188
+ """
189
+ if not all_results:
190
+ return DataFrame()
191
+
192
+ roo_frame = model_source.copy()
193
+ required_positions = ['FLEX1', 'FLEX2', 'FLEX3', 'FLEX4', 'FLEX5', 'FLEX6']
194
+
195
+ combined_df = pd_concat(all_results, ignore_index=True)
196
+ combined_df = combined_df.sort_values(by='Total_Median', ascending=False)
197
+
198
+ # Process each row to create the formatted structure
199
+ formatted_rows = []
200
+
201
+ for _, row in combined_df.iterrows():
202
+ new_row = {}
203
+
204
+ # Basic columns
205
+ new_row['salary'] = row['Total_Salary']
206
+ new_row['proj'] = row['Total_Median']
207
+ new_row['Own'] = row['Own']
208
+
209
+ # Add position columns
210
+ for pos in required_positions:
211
+ if pos in row:
212
+ new_row[pos] = row[pos]
213
+
214
+ formatted_rows.append(new_row)
215
+
216
+ final_formatted_df = DataFrame(formatted_rows)
217
+
218
+ # Ensure all required position columns exist
219
+ for pos in required_positions:
220
+ if pos not in final_formatted_df.columns:
221
+ final_formatted_df[pos] = ''
222
+
223
+ # Reorder columns
224
+ column_order = ['salary', 'proj', 'Own'] + required_positions
225
+ final_formatted_df = final_formatted_df[column_order]
226
+
227
+ return final_formatted_df
228
+
229
+ def player_level_classic_roo(working_proj: DataFrame, stat_dicts: dict, client: MongoClient, total_sims: int = 1000):
230
+ db = client['NASCAR_Database']
231
+
232
+ flex_file = working_proj[['Player', 'Position', 'ID', 'Salary', 'Grid', 'Laps Led', 'Fast Laps', 'floor_avg', 'ceiling_avg', 'Median', 'Own']]
233
+ flex_file['Floor'] = flex_file['Median'] * (flex_file['floor_avg'])
234
+ flex_file['Ceiling'] = flex_file['Median'] * (1+flex_file['ceiling_avg'])
235
+ print(flex_file[['Player', 'floor_avg', 'Floor', 'Median', 'ceiling_avg', 'Ceiling']])
236
+ flex_file['STD'] = (flex_file['Ceiling'] / ((1 - flex_file['ceiling_avg']) * 10))
237
+ flex_file = flex_file[['Player', 'Position', 'Salary', 'Grid', 'Laps Led', 'Fast Laps', 'Floor', 'Median', 'Ceiling', 'STD']]
238
+ flex_file = flex_file.reset_index(drop=True)
239
+ hold_file = flex_file.copy()
240
+ overall_file = flex_file.copy()
241
+ salary_file = flex_file.copy()
242
+
243
+ try:
244
+ overall_median_gpu = np_array(overall_file['Median'])
245
+ overall_std_gpu = np_array(overall_file['STD'])
246
+ overall_salary_gpu = np_array(overall_file['Salary'])
247
+
248
+ data_shape = (len(overall_file['Player']), total_sims)
249
+ salary_array = np_zeros(data_shape)
250
+ sim_array = np_zeros(data_shape)
251
+
252
+ for x in range(0, total_sims):
253
+ result_gpu = overall_salary_gpu
254
+ salary_array[:, x] = result_gpu
255
+ cupy_array = salary_array
256
+
257
+ salary_file = salary_file.reset_index(drop=True)
258
+ salary_cupy = DataFrame(cupy_array, columns=list(range(0, total_sims)))
259
+ salary_check_file = pd_concat([salary_file, salary_cupy], axis=1)
260
+ except:
261
+ for x in range(0,total_sims):
262
+ salary_file[x] = salary_file['Salary']
263
+ salary_check_file = salary_file.copy()
264
+
265
+ salary_file=salary_check_file.drop(['Player', 'Position', 'Salary', 'Floor', 'Median', 'Ceiling', 'STD'], axis=1)
266
+
267
+ salary_file = salary_file.div(total_sims)
268
+
269
+ try:
270
+ for x in range(0, total_sims):
271
+ result_gpu = np_random.normal(overall_median_gpu, overall_std_gpu)
272
+ sim_array[:, x] = result_gpu
273
+ add_array = sim_array
274
+
275
+ overall_file = overall_file.reset_index(drop=True)
276
+ df2 = DataFrame(add_array, columns=list(range(0, total_sims)))
277
+ check_file = pd_concat([overall_file, df2], axis=1)
278
+ except:
279
+ for x in range(0,total_sims):
280
+ overall_file[x] = np_random.normal(overall_file['Median'],overall_file['STD'])
281
+ check_file = overall_file.copy()
282
+
283
+ overall_file=check_file.drop(['Player', 'Position', 'Salary', 'Floor', 'Median', 'Ceiling', 'STD'], axis=1)
284
+
285
+ players_only = hold_file[['Player']]
286
+ raw_lineups_file = players_only
287
+
288
+ for x in range(0,total_sims):
289
+ maps_dict = {'proj_map':dict(zip(hold_file.Player,overall_file[x]))}
290
+ raw_lineups_file[x] = sum([raw_lineups_file['Player'].map(maps_dict['proj_map'])])
291
+ players_only[x] = raw_lineups_file[x].rank(ascending=False)
292
+
293
+ players_only=players_only.drop(['Player'], axis=1)
294
+
295
+ salary_low_check = (overall_file - (salary_file*6))
296
+ salary_mid_check = (overall_file - (salary_file*7))
297
+ salary_high_check = (overall_file - (salary_file*8))
298
+ gpp_check = (overall_file - ((salary_file*6)+10))
299
+
300
+ players_only['Average_Rank'] = players_only.mean(axis=1)
301
+ players_only['Top_finish'] = (players_only[players_only == 1].count(axis=1)/total_sims).clip(upper=1, lower=0)
302
+ players_only['Top_3_finish'] = (players_only[players_only <= 3].count(axis=1)/total_sims).clip(upper=1, lower=0)
303
+ players_only['Top_5_finish'] = (players_only[players_only <= 5].count(axis=1)/total_sims).clip(upper=1, lower=0)
304
+ players_only['Top_10_finish'] = (players_only[players_only <= 10].count(axis=1)/total_sims).clip(upper=1, lower=0)
305
+ players_only['Top_15_finish'] = (players_only[players_only <= 15].count(axis=1)/total_sims).clip(upper=1, lower=0)
306
+ players_only['Top_20_finish'] = (players_only[players_only <= 20].count(axis=1)/total_sims).clip(upper=1, lower=0)
307
+ players_only['Top_25_finish'] = (players_only[players_only <= 25].count(axis=1)/total_sims).clip(upper=1, lower=0)
308
+ players_only['Top_30_finish'] = (players_only[players_only <= 30].count(axis=1)/total_sims).clip(upper=1, lower=0)
309
+
310
+ players_only['Player'] = hold_file[['Player']]
311
+
312
+ final_outcomes = players_only[['Player', 'Top_finish', 'Top_3_finish', 'Top_5_finish', 'Top_10_finish', 'Top_15_finish', 'Top_20_finish', 'Top_25_finish', 'Top_30_finish']]
313
+
314
+ final_Proj = pd_merge(hold_file, final_outcomes, on="Player")
315
+ final_Proj = final_Proj[['Player', 'Position', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_3_finish', 'Top_5_finish', 'Top_10_finish', 'Top_15_finish', 'Top_20_finish', 'Top_25_finish', 'Top_30_finish']]
316
+
317
+ final_Proj['Own'] = final_Proj['Player'].map(stat_dicts['Own%'])
318
+ final_Proj = final_Proj.replace('', np_nan)
319
+ final_Proj = final_Proj.dropna(subset=['Own'])
320
+ final_Proj['Own'] = final_Proj['Own'].astype('float')
321
+ final_Proj['Small_Own'] = final_Proj['Own'] + (.2 * (final_Proj['Own'] - final_Proj['Own'].mean()))
322
+ own_norm = 600 / final_Proj['Small_Own'].sum()
323
+ final_Proj['Small_Own'] = final_Proj['Small_Own'] * own_norm
324
+ final_Proj['Large_Own'] = final_Proj['Own'] - (.2 * (final_Proj['Own'] - final_Proj['Own'].mean()))
325
+ own_norm = 600 / final_Proj['Large_Own'].sum()
326
+ final_Proj['Large_Own'] = final_Proj['Large_Own'] * own_norm
327
+ final_Proj['Cash_Own'] = final_Proj['Own'] + (.33 * (final_Proj['Own'] - final_Proj['Own'].mean()))
328
+ own_norm = 600 / final_Proj['Cash_Own'].sum()
329
+ final_Proj['Cash_Own'] = final_Proj['Cash_Own'] * own_norm
330
+ final_Proj['Own'] = final_Proj['Own'].clip(upper=85, lower=0)
331
+ final_Proj['Small_Own'] = final_Proj['Small_Own'].clip(upper=95, lower=.01)
332
+ final_Proj['Large_Own'] = final_Proj['Large_Own'].clip(upper=80, lower=.1)
333
+ final_Proj['Cash_Own'] = final_Proj['Cash_Own'].clip(upper=99, lower=0)
334
+ final_Proj['CPT_Own'] = final_Proj['Own'] / 6
335
+ final_Proj['slate'] = 'Main Slate'
336
+ final_Proj['site'] = 'Draftkings'
337
+ final_Proj['version'] = 'overall'
338
+ final_Proj['player_id'] = final_Proj['Player'].map(stat_dicts['ID'])
339
+ final_Proj.insert(1, 'Grid', final_Proj['Player'].map(stat_dicts['Grid']))
340
+ Overall_Proj = final_Proj.sort_values(by='Median', ascending=False)
341
+
342
+ collection = db['Player_Level_ROO']
343
+ Overall_Proj = Overall_Proj.reset_index(drop=True)
344
+ chunk_size = 100000
345
+ collection.drop()
346
+ for i in range(0, len(Overall_Proj), chunk_size):
347
+ for _ in range(5):
348
+ try:
349
+ df_chunk = Overall_Proj.iloc[i:i + chunk_size]
350
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
351
+ break
352
+ except Exception as e:
353
+ print(f"Retry due to error: {e}")
354
+ time_sleep(1)
355
+
356
+ return Overall_Proj.copy()
357
+
358
+ def DK_seed_frame(working_proj: DataFrame, stat_dicts: dict, client: MongoClient, contestType: str):
359
+
360
+ db = client['NASCAR_Database']
361
+
362
+ source_frame = working_proj.copy()
363
+ optimal_lineups = []
364
+
365
+ Overall_Proj = source_frame.copy()
366
+
367
+ Overall_Proj['salary_Value'] = (Overall_Proj['Salary'] / 1000) / Overall_Proj['Median']
368
+ Overall_Proj['proj_Value'] = Overall_Proj['Median'].rank(pct = True)
369
+ Overall_Proj['own_Value'] = Overall_Proj['Own'].rank(pct = True)
370
+ Overall_Proj['sort_Value'] = Overall_Proj[['own_Value', 'salary_Value']].mean(axis=1)
371
+ Overall_Proj = Overall_Proj.sort_values(by='own_Value', ascending=False)
372
+ Overall_Proj.rename(columns={"Player": "Name"}, inplace = True)
373
+ Overall_Proj = Overall_Proj.dropna()
374
+ Overall_Proj = Overall_Proj.reset_index(drop=True)
375
+
376
+ players_median = Overall_Proj.drop_duplicates(subset ='Name', keep ='first')
377
+
378
+ players_median['Var'] = players_median.index
379
+
380
+ # Add slate identifier and collect data for JSON export
381
+ players_median_copy = players_median.copy()
382
+ players_median_copy['slate'] = 'Main Slate'
383
+
384
+ # Create maps for Go processing
385
+ players_name_map = {str(int(idx)): str(name) for idx, name in players_median_copy.set_index('Var')['Name'].items()}
386
+ players_salary_map = {str(int(idx)): int(salary) for idx, salary in players_median_copy.set_index('Var')['Salary'].items()}
387
+ players_projection_map = {str(int(idx)): float(proj) for idx, proj in players_median_copy.set_index('Var')['Median'].items()}
388
+ players_ownership_map = {str(int(idx)): float(own) for idx, own in players_median_copy.set_index('Var')['Own'].items()}
389
+
390
+ # Create output data structure for Go
391
+ output_data = {
392
+ "players_median": {
393
+ "players": [],
394
+ "maps": {
395
+ "name_map": players_name_map,
396
+ "salary_map": players_salary_map,
397
+ "projection_map": players_projection_map,
398
+ "ownership_map": players_ownership_map
399
+ }
400
+ }
401
+ }
402
+
403
+ # Convert players to Go struct format
404
+ for idx, row in players_median_copy.iterrows():
405
+ player = {
406
+ "id": int(row['Var']),
407
+ "name": str(row['Name']),
408
+ "position": str(row['Position']),
409
+ "salary": int(row['Salary']),
410
+ "projection": float(row['Median']),
411
+ "ownership": float(row['Own']),
412
+ "salary_value": float(row['salary_Value']),
413
+ "proj_value": float(row['proj_Value']),
414
+ "own_value": float(row['own_Value']),
415
+ "sort_value": float(row['sort_Value']),
416
+ "slate": 'Main Slate'
417
+ }
418
+ output_data["players_median"]["players"].append(player)
419
+
420
+ # Write JSON data for Go processing
421
+ with open(f'nascar_go/player_data.json', 'w') as f:
422
+ json.dump(output_data, f)
423
+
424
+ collection = db[f'DK_NASCAR_{contestType}_name_map']
425
+
426
+ master_name_map = pd_Series(players_median.Name.values,index=players_median.Var).to_dict()
427
+ master_name_index = pd_Series(players_median.Var.values, index=players_median.Name).to_dict()
428
+
429
+ position_requirements = {
430
+ 'FLEX': 6
431
+ }
432
+
433
+ salary_cap = 50000
434
+ max_team_players = 6
435
+
436
+ required_positions = ['FLEX1', 'FLEX2', 'FLEX3', 'FLEX4', 'FLEX5', 'FLEX6']
437
+
438
+ collection.drop()
439
+ try:
440
+ # Convert dictionary to format suitable for MongoDB
441
+ mongo_docs = [{"key": k, "value": v} for k, v in master_name_map.items()]
442
+ collection.insert_many(mongo_docs, ordered=False)
443
+ except Exception as e:
444
+ print(f"Error inserting name map: {e}")
445
+ time_sleep(1)
446
+
447
+ optimals = init_nascar_optimals(working_proj, salary_cap, 1000)
448
+ formatted_optimals = format_nascar_optimals(optimals, working_proj)
449
+
450
+ for col in required_positions:
451
+ if col in formatted_optimals.columns:
452
+ formatted_optimals[col] = formatted_optimals[col].map(master_name_index).fillna(formatted_optimals[col])
453
+
454
+ formatted_optimals['proj'] = formatted_optimals['proj'].astype(float)
455
+
456
+ # Convert this slate's optimals to JSON format and add slate info
457
+ for idx, row in formatted_optimals.iterrows():
458
+ optimal_lineup = {
459
+ "slate": 'Main Slate', # Add slate identifier
460
+ "salary": int(row['salary']),
461
+ "projection": float(row['proj']),
462
+ "ownership": float(row['Own']),
463
+ "players": [int(row['FLEX1']), int(row['FLEX2']), int(row['FLEX3']),
464
+ int(row['FLEX4']), int(row['FLEX5']), int(row['FLEX6'])]
465
+ }
466
+ optimal_lineups.append(optimal_lineup)
467
+
468
+ print(f"Generated {len(formatted_optimals)} optimal lineups for slate Main Slate")
469
+
470
+ with open(f'nascar_go/optimal_lineups.json', 'w') as f:
471
+ json.dump(optimal_lineups, f)
472
+
473
+ run_go_classic_lineup_generator("DK", "NASCAR", contestType)
474
+ print("NASCAR lineup generation for DK completed successfully!")
src/streamlit_app.py CHANGED
@@ -76,12 +76,13 @@ sport_icons = {
76
  "NBA": "πŸ€",
77
  "MLB": "⚾",
78
  "PGA": "β›³",
79
- "MMA": "πŸ₯Š"
 
80
  }
81
 
82
  selected_tab = st.segmented_control(
83
  "Select Tab",
84
- options=["NHL Updates", "NBA Updates", 'MLB Updates', 'NFL Updates', 'PGA Updates', 'MMA Updates'],
85
  selection_mode='single',
86
  default='NHL Updates',
87
  width='stretch',
@@ -638,3 +639,89 @@ elif selected_tab == "MMA Updates":
638
 
639
  st.success("βœ… MMA updates completed successfully!")
640
  st.balloons()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  "NBA": "πŸ€",
77
  "MLB": "⚾",
78
  "PGA": "β›³",
79
+ "MMA": "πŸ₯Š",
80
+ "NASCAR": "🏎",
81
  }
82
 
83
  selected_tab = st.segmented_control(
84
  "Select Tab",
85
+ options=["NHL Updates", "NBA Updates", 'MLB Updates', 'NFL Updates', 'PGA Updates', 'MMA Updates', 'NASCAR Updates'],
86
  selection_mode='single',
87
  default='NHL Updates',
88
  width='stretch',
 
639
 
640
  st.success("βœ… MMA updates completed successfully!")
641
  st.balloons()
642
+
643
+ # ============================================================================
644
+ # NASCAR UPDATES TAB
645
+ # ============================================================================
646
+ elif selected_tab == "NASCAR Updates":
647
+ from sports.nascar_functions import *
648
+
649
+ st.header("🏎 NASCAR Model Updates")
650
+
651
+ # Column definitions
652
+ str_columns = ['Name', 'Roster Position', 'Site', 'Type']
653
+ float_columns = ['Grid', 'Laps Led', 'Fast Laps', 'FloorVar', 'CeilingVar', 'Fantasy', 'Own%']
654
+ int_columns = ['Salary', 'ID']
655
+ stat_columns = float_columns + int_columns
656
+
657
+ if st.button("πŸ”„ Run NASCAR Updates", type="primary", use_container_width=True):
658
+ with st.spinner("Updating NASCAR models..."):
659
+ st.write("Loading NASCAR data from Google Sheets...")
660
+ try:
661
+ sh = gc.open_by_url(NASCAR_Master_Hold)
662
+ worksheet = sh.worksheet('Export')
663
+ # worksheet = sh.worksheet('Test_DK_Projections')
664
+ projects_raw = DataFrame(worksheet.get_values())
665
+ except:
666
+ sh = gc2.open_by_url(NASCAR_Master_Hold)
667
+ worksheet = sh.worksheet('Export')
668
+ # worksheet = sh.worksheet('Test_DK_Projections')
669
+ projects_raw = DataFrame(worksheet.get_values())
670
+ projects_raw.columns = projects_raw.iloc[0]
671
+ projects_raw = projects_raw[1:]
672
+ projects_raw = projects_raw.reset_index(drop=True)
673
+ projects_raw = projects_raw.replace('', np_nan)
674
+ projects_raw = projects_raw.dropna(subset=['Salary'])
675
+
676
+ for col in str_columns:
677
+ projects_raw[col] = projects_raw[col].astype(str)
678
+ for col in float_columns:
679
+ projects_raw[col] = projects_raw[col].str.replace('%', '').astype(float)
680
+ for col in int_columns:
681
+ projects_raw[col] = projects_raw[col].str.replace('%', '').astype(int)
682
+
683
+ projects_raw = projects_raw[projects_raw['Fantasy'] > 0]
684
+ projects_raw['Fantasy'] = projects_raw['Fantasy']
685
+ projects_raw['Own%'] = projects_raw['Own%']
686
+
687
+ classic_proj = projects_raw[projects_raw['Type'] == 'Classic']
688
+
689
+ classic_stat_dicts = {}
690
+
691
+ for col in stat_columns:
692
+ classic_stat_dicts[col] = dict(zip(classic_proj['Name'], classic_proj[col]))
693
+
694
+ classic_working_proj = classic_proj[['Name', 'Roster Position', 'Site', 'Type', 'Salary', 'ID', 'Grid', 'Laps Led', 'Fast Laps', 'FloorVar', 'CeilingVar', 'Fantasy', 'Own%']]
695
+ classic_working_proj = classic_working_proj.rename(columns={"Name": "Player", "Roster Position": "Position", "Fantasy": "Median", "Own%": "Own", "FloorVar": "floor_avg", "CeilingVar": "ceiling_avg"})
696
+
697
+ st.write("Generating NASCAR ROO structure...")
698
+ Overall_Proj = player_level_classic_roo(classic_working_proj, classic_stat_dicts, client)
699
+ st.write("NASCAR Draftkings ROO structure refreshed")
700
+ st.table(Overall_Proj.head(10))
701
+
702
+ try:
703
+ discord.post(content="NASCAR Draftkings ROO structure refreshed")
704
+ except:
705
+ pass
706
+
707
+ # Upload to database
708
+ collection = nascar_db['Player_Level_ROO']
709
+ Overall_Proj_copy = Overall_Proj.reset_index(drop=True)
710
+ chunk_size = 100000
711
+ collection.drop()
712
+ for i in range(0, len(Overall_Proj_copy), chunk_size):
713
+ for _ in range(5):
714
+ try:
715
+ df_chunk = Overall_Proj_copy.iloc[i:i + chunk_size]
716
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
717
+ break
718
+ except Exception as e:
719
+ st.write(f"Retry due to error: {e}")
720
+ time_sleep(1)
721
+
722
+ st.write("Generating NASCAR seed frames...")
723
+ DK_seed_frame(Overall_Proj, classic_stat_dicts, client, "Classic")
724
+ st.write("NASCAR Draftkings Seed Frames refreshed")
725
+
726
+ st.success("βœ… NASCAR updates completed successfully!")
727
+ st.balloons()