James McCool commited on
Commit
df1fe2e
Β·
1 Parent(s): 8c6c2fc

Adding PGA functionality, changed dockerfile, added Go executable, changed streamlit_app and PGA_functions.

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