James McCool commited on
Commit
61b5f4b
Β·
1 Parent(s): 30d69ab

Adding MMA functionality to Dockerfile as well as adding any Go executables, change the database to grab from the MMA database, change the Streamlit app and the MMA_functions to add functionality.

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