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 +4 -0
- func/pga_go/PGA_seed_frames.go +973 -0
- src/sports/pga_functions.py +631 -0
- src/streamlit_app.py +84 -3
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()
|