James McCool commited on
Commit
85eb84f
Β·
1 Parent(s): 9c0a74e

Adding NFL functionality

Browse files
CHANGES.md DELETED
@@ -1,303 +0,0 @@
1
- # πŸ”§ Changes Summary - Paydirt NHL Model Updates
2
-
3
- ## Overview
4
-
5
- Successfully converted your Python script into a production-ready Streamlit app with proper configuration for Hugging Face deployment.
6
-
7
- ---
8
-
9
- ## βœ… Issues Fixed
10
-
11
- ### 1. Missing Python Dependencies βœ“
12
- **Problem**: `requirements.txt` only had 3 packages
13
- **Solution**: Added all required dependencies with version pinning
14
-
15
- **Updated `requirements.txt`:**
16
- - streamlit==1.32.0
17
- - pandas==2.2.0
18
- - numpy==1.26.4
19
- - altair==5.2.0
20
- - pytz==2024.1
21
- - ortools==9.9.3963
22
- - gspread==6.0.2
23
- - discordwebhook==1.0.3
24
- - pymongo==4.6.2
25
- - xgboost==2.0.3
26
- - lightgbm==4.3.0
27
- - scikit-learn==1.4.1.post1
28
-
29
- ### 2. Import Path for NHL_own_regress βœ“
30
- **Problem**: Module was in `func/` directory, not in Python path
31
- **Solution**: Added path adjustment in `streamlit_app.py`
32
-
33
- ```python
34
- import sys
35
- sys.path.append('../func')
36
- from NHL_own_regress import xgb_model, lgb_model, knn_model
37
- ```
38
-
39
- ### 3. Go Executable Compatibility βœ“
40
- **Problem**: Windows `.exe` files won't work in Docker Linux container
41
- **Solution**: Multi-stage Docker build that compiles Go programs for Linux
42
-
43
- **Updated `Dockerfile`:**
44
- - Stage 1: Builds Go binaries from source for Linux
45
- - Stage 2: Creates Python runtime with compiled binaries
46
- - Optimized with Alpine Linux for Go build
47
- - Proper file permissions set
48
-
49
- ### 4. Hardcoded Credentials βœ“
50
- **Problem**: Service account keys and URIs exposed in code
51
- **Solution**: Environment variable support with fallbacks
52
-
53
- **Added to `streamlit_app.py`:**
54
- ```python
55
- def get_secret(key, default=None):
56
- """Get secret from Streamlit secrets or environment variables"""
57
- try:
58
- return st.secrets[key]
59
- except:
60
- return os.getenv(key, default)
61
- ```
62
-
63
- All credentials now configurable via:
64
- - Streamlit secrets (`.streamlit/secrets.toml`)
65
- - Environment variables
66
- - Hugging Face Space secrets
67
-
68
- ### 5. Streamlit UI Components βœ“
69
- **Problem**: No proper Streamlit interface
70
- **Solution**: Added comprehensive UI
71
-
72
- **Added UI Elements:**
73
- - Page configuration with title and icon
74
- - Sidebar with app info and connection status
75
- - Visual connection indicators for MongoDB and Google Sheets
76
- - Styled primary button for lineup generation
77
- - Progress messages throughout execution
78
- - Success message and celebration animation on completion
79
-
80
- ---
81
-
82
- ## πŸ“ New Files Created
83
-
84
- ### Configuration Files
85
-
86
- 1. **`.gitignore`**
87
- - Prevents committing sensitive files
88
- - Includes Python, IDE, and Streamlit patterns
89
-
90
- 2. **`.dockerignore`**
91
- - Optimizes Docker build
92
- - Excludes unnecessary files from image
93
-
94
- 3. **`.streamlit/config.toml`**
95
- - Streamlit app configuration
96
- - Custom theme settings
97
- - Server configuration for Hugging Face
98
-
99
- 4. **`.streamlit/secrets.toml.example`**
100
- - Template for local development secrets
101
- - Documents all required credentials
102
-
103
- ### Documentation
104
-
105
- 5. **`README.md`** (Replaced)
106
- - Comprehensive project documentation
107
- - Setup instructions for local and cloud
108
- - Architecture overview
109
- - Technology stack details
110
-
111
- 6. **`DEPLOYMENT.md`**
112
- - Step-by-step Hugging Face deployment guide
113
- - Troubleshooting section
114
- - Security checklist
115
- - Monitoring and maintenance procedures
116
-
117
- 7. **`CHANGES.md`** (This file)
118
- - Summary of all modifications
119
- - Before/after comparisons
120
-
121
- ---
122
-
123
- ## πŸ”„ Modified Files
124
-
125
- ### `streamlit_app.py`
126
-
127
- **Line 54-59**: Added path adjustment for NHL_own_regress import
128
- ```python
129
- import sys
130
- sys.path.append('../func')
131
- from NHL_own_regress import xgb_model, lgb_model, knn_model
132
- ```
133
-
134
- **Line 70-129**: Replaced hardcoded credentials with environment variable support
135
- - Added `get_secret()` helper function
136
- - Updated all credential dictionaries to use `get_secret()`
137
- - Applied to MongoDB URI, Discord webhook, Google Sheets URL
138
-
139
- **Line 137-156**: Fixed subprocess calls for Go binaries
140
- - Changed `.exe` to Linux binary names
141
- - Updated working directory path
142
- - Already had `st.write()` calls for progress
143
-
144
- **Line 2117-2151**: Added comprehensive Streamlit UI
145
- - Page configuration
146
- - Title and layout
147
- - Sidebar with info and status
148
- - Connection indicators
149
- - Styled button
150
-
151
- **Line 2317-2321**: Fixed ending logic
152
- - Removed old loop variables (`x`, `high_end`)
153
- - Added success message and balloons
154
- - Clean MongoDB connection closure
155
-
156
- ### `Dockerfile`
157
-
158
- **Complete rewrite** with multi-stage build:
159
-
160
- **Stage 1 - Go Builder:**
161
- ```dockerfile
162
- FROM golang:1.24-alpine AS go-builder
163
- # Builds DK and FD NHL seed frame generators
164
- ```
165
-
166
- **Stage 2 - Python Runtime:**
167
- ```dockerfile
168
- FROM python:3.13-slim
169
- # Copies Go binaries and sets up Python environment
170
- ```
171
-
172
- ### `requirements.txt`
173
-
174
- **Before:**
175
- ```
176
- altair
177
- pandas
178
- streamlit
179
- ```
180
-
181
- **After:**
182
- ```
183
- streamlit==1.32.0
184
- pandas==2.2.0
185
- numpy==1.26.4
186
- # ... 9 more packages with versions
187
- ```
188
-
189
- ---
190
-
191
- ## 🎯 Features Added
192
-
193
- ### Security Enhancements
194
- - βœ“ Environment variable support
195
- - βœ“ Streamlit secrets integration
196
- - βœ“ Hugging Face Spaces secrets compatibility
197
- - βœ“ `.gitignore` for sensitive files
198
- - βœ“ Default values for local development
199
-
200
- ### User Experience
201
- - βœ“ Professional UI with icons
202
- - βœ“ Real-time connection status
203
- - βœ“ Progress indicators
204
- - βœ“ Success notifications
205
- - βœ“ Celebration animation
206
- - βœ“ Sidebar information panel
207
-
208
- ### DevOps
209
- - βœ“ Multi-stage Docker build
210
- - βœ“ Optimized image size
211
- - βœ“ Health check endpoint
212
- - βœ“ Proper build caching
213
- - βœ“ Cross-platform compatibility
214
-
215
- ### Documentation
216
- - βœ“ Comprehensive README
217
- - βœ“ Deployment guide
218
- - βœ“ Architecture documentation
219
- - βœ“ Configuration templates
220
- - βœ“ Security checklists
221
-
222
- ---
223
-
224
- ## πŸš€ Ready for Deployment
225
-
226
- Your app is now ready to deploy on Hugging Face Spaces!
227
-
228
- ### Quick Start
229
-
230
- 1. **Create Space** on Hugging Face (Docker SDK)
231
- 2. **Add Secrets** from `.streamlit/secrets.toml.example`
232
- 3. **Push Code** to Space repository
233
- 4. **Monitor Build** (8-12 minutes first time)
234
- 5. **Test App** and verify connections
235
-
236
- ### What Happens on Deploy
237
-
238
- 1. Docker builds Go programs for Linux βœ“
239
- 2. Installs Python dependencies βœ“
240
- 3. Configures Streamlit βœ“
241
- 4. Starts app on port 8501 βœ“
242
- 5. Auto-restarts on file changes [[memory:6085392]] βœ“
243
-
244
- ---
245
-
246
- ## πŸ“‹ Pre-Deployment Checklist
247
-
248
- - [ ] Review `DEPLOYMENT.md` for detailed instructions
249
- - [ ] Prepare all secrets (MongoDB, Google, Discord)
250
- - [ ] Create Hugging Face Space
251
- - [ ] Add secrets to Space settings
252
- - [ ] Push code to Space repository
253
- - [ ] Monitor build logs
254
- - [ ] Test functionality
255
- - [ ] Verify Discord notifications
256
- - [ ] Check Google Sheets updates
257
- - [ ] Confirm MongoDB writes
258
-
259
- ---
260
-
261
- ## πŸ” Security Reminders
262
-
263
- 1. **Never commit** `.streamlit/secrets.toml`
264
- 2. **Use Hugging Face Secrets** for production
265
- 3. **Rotate credentials** regularly
266
- 4. **Review permissions** on service accounts
267
- 5. **Keep Space private** if handling sensitive data
268
-
269
- ---
270
-
271
- ## πŸ“š Next Steps
272
-
273
- 1. Test locally: `streamlit run src/streamlit_app.py`
274
- 2. Follow `DEPLOYMENT.md` for Hugging Face deployment
275
- 3. Set up monitoring and alerts
276
- 4. Document any custom configurations
277
- 5. Train team on using the app
278
-
279
- ---
280
-
281
- ## πŸ› Known Limitations
282
-
283
- 1. **Linter Warnings**: Import warnings are false positives (packages not in linter env)
284
- 2. **First Build**: Takes 8-12 minutes on Hugging Face
285
- 3. **Connection Errors**: Require proper secret configuration
286
- 4. **Go Binaries**: Only work in Docker (Linux), need `.exe` for local Windows
287
-
288
- ---
289
-
290
- ## πŸ’‘ Support
291
-
292
- If you encounter issues:
293
-
294
- 1. Check `DEPLOYMENT.md` troubleshooting section
295
- 2. Review Streamlit and Hugging Face logs
296
- 3. Verify all secrets are configured correctly
297
- 4. Test locally with same secrets
298
- 5. Contact development team
299
-
300
- ---
301
-
302
- **All changes have been tested and are ready for deployment!** πŸŽ‰
303
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
DEPLOYMENT.md DELETED
@@ -1,278 +0,0 @@
1
- # πŸš€ Deployment Guide - Paydirt NHL Model Updates
2
-
3
- ## Hugging Face Spaces Deployment
4
-
5
- ### Prerequisites
6
-
7
- 1. Hugging Face account
8
- 2. Service account credentials for Google Sheets API
9
- 3. MongoDB Atlas connection string
10
- 4. Discord webhook URL (optional)
11
-
12
- ### Step-by-Step Deployment
13
-
14
- #### 1. Create a New Space
15
-
16
- 1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
17
- 2. Click "Create new Space"
18
- 3. Configure:
19
- - **Name**: paydirt-nhl-model-updates (or your choice)
20
- - **SDK**: Docker
21
- - **Hardware**: CPU Basic (upgrade if needed)
22
- - **Visibility**: Private (recommended for production data)
23
-
24
- #### 2. Configure Secrets
25
-
26
- Go to your Space Settings β†’ Repository secrets and add:
27
-
28
- ```
29
- MONGODB_URI = "mongodb+srv://username:password@cluster.mongodb.net/..."
30
- GOOGLE_PROJECT_ID_1 = "your-project-id"
31
- GOOGLE_PRIVATE_KEY_ID_1 = "your-key-id"
32
- GOOGLE_PRIVATE_KEY_1 = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
33
- GOOGLE_CLIENT_EMAIL_1 = "service-account@project.iam.gserviceaccount.com"
34
- GOOGLE_CLIENT_ID_1 = "123456789"
35
- GOOGLE_PROJECT_ID_2 = "backup-project-id"
36
- GOOGLE_PRIVATE_KEY_ID_2 = "backup-key-id"
37
- GOOGLE_PRIVATE_KEY_2 = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
38
- GOOGLE_CLIENT_EMAIL_2 = "backup-service-account@project.iam.gserviceaccount.com"
39
- GOOGLE_CLIENT_ID_2 = "987654321"
40
- DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/..."
41
- MASTER_SHEET_URL = "https://docs.google.com/spreadsheets/d/..."
42
- ```
43
-
44
- **Important Notes:**
45
- - For private keys, keep the `\n` characters for newlines
46
- - Values will be automatically available as Streamlit secrets
47
- - Never commit these values to the repository
48
-
49
- #### 3. Push Code to Space
50
-
51
- **Option A: Using Git**
52
-
53
- ```bash
54
- # Clone your Space repository
55
- git clone https://huggingface.co/spaces/YOUR_USERNAME/paydirt-nhl-model-updates
56
- cd paydirt-nhl-model-updates
57
-
58
- # Copy your code
59
- cp -r /path/to/Paydirt_model_updates/* .
60
-
61
- # Commit and push
62
- git add .
63
- git commit -m "Initial deployment"
64
- git push
65
- ```
66
-
67
- **Option B: Using Hugging Face Web Interface**
68
-
69
- 1. Go to Files β†’ Add file
70
- 2. Upload all files from `Paydirt_model_updates/` directory
71
- 3. Commit changes
72
-
73
- #### 4. Monitor Build
74
-
75
- 1. Go to your Space's "Building" tab
76
- 2. Watch the Docker build logs
77
- 3. Build should complete in 5-10 minutes
78
- 4. Space will automatically start once build succeeds
79
-
80
- #### 5. Verify Deployment
81
-
82
- 1. Open your Space URL
83
- 2. Check sidebar for connection status
84
- 3. Test the "Generate NHL Lineups" button
85
- 4. Monitor Discord for notification
86
- 5. Verify data updates in Google Sheets and MongoDB
87
-
88
- ---
89
-
90
- ## Docker Build Details
91
-
92
- ### Build Stages
93
-
94
- 1. **Go Builder Stage**
95
- - Compiles DK and FD lineup generators
96
- - Uses Alpine Linux for minimal size
97
- - Produces static binaries
98
-
99
- 2. **Python Runtime Stage**
100
- - Installs Python dependencies
101
- - Copies Go binaries
102
- - Configures Streamlit
103
-
104
- ### Build Time
105
-
106
- - First build: ~8-12 minutes
107
- - Subsequent builds: ~3-5 minutes (with layer caching)
108
-
109
- ### Troubleshooting Build Issues
110
-
111
- **Go Build Fails:**
112
- ```bash
113
- # Check go.mod and go.sum are present
114
- # Verify Go version compatibility (1.24)
115
- ```
116
-
117
- **Python Dependencies Fail:**
118
- ```bash
119
- # Check requirements.txt format
120
- # Verify all packages are available on PyPI
121
- # Consider pinning versions for stability
122
- ```
123
-
124
- **Large Image Size:**
125
- ```bash
126
- # Current size: ~2-3 GB
127
- # Optimize by:
128
- # - Using slimmer base images
129
- # - Multi-stage builds (already implemented)
130
- # - Cleaning up apt cache (already implemented)
131
- ```
132
-
133
- ---
134
-
135
- ## Environment-Specific Configuration
136
-
137
- ### Development (Local)
138
-
139
- ```bash
140
- # Use .streamlit/secrets.toml for local secrets
141
- streamlit run src/streamlit_app.py
142
- ```
143
-
144
- ### Staging/Production (Hugging Face)
145
-
146
- - Secrets managed via Space Settings
147
- - Automatic builds on push
148
- - Zero-downtime deployments
149
-
150
- ---
151
-
152
- ## Monitoring & Maintenance
153
-
154
- ### Health Checks
155
-
156
- The app includes a health check endpoint at `/_stcore/health`
157
-
158
- ### Logs
159
-
160
- View logs in Hugging Face Spaces:
161
- 1. Go to your Space
162
- 2. Click "Logs" tab
163
- 3. Monitor for errors or warnings
164
-
165
- ### Performance
166
-
167
- - CPU Basic sufficient for typical use
168
- - Upgrade to CPU Upgrade for:
169
- - Larger datasets
170
- - More frequent runs
171
- - Multiple concurrent users
172
-
173
- ### Updates
174
-
175
- To update the app:
176
-
177
- ```bash
178
- # Pull latest changes
179
- git pull origin main
180
-
181
- # Make changes
182
- # ...
183
-
184
- # Push updates
185
- git add .
186
- git commit -m "Update: [description]"
187
- git push
188
- ```
189
-
190
- Space will automatically rebuild and redeploy.
191
-
192
- ---
193
-
194
- ## Security Checklist
195
-
196
- - [ ] All secrets configured in Space settings (not in code)
197
- - [ ] Space visibility set to Private
198
- - [ ] Service account has minimal required permissions
199
- - [ ] MongoDB connection uses restricted user
200
- - [ ] Discord webhook secured
201
- - [ ] Google Sheets shared only with service accounts
202
- - [ ] Regular credential rotation schedule established
203
-
204
- ---
205
-
206
- ## Rollback Procedure
207
-
208
- If deployment fails:
209
-
210
- 1. Go to Space Settings β†’ Files
211
- 2. Find previous working commit
212
- 3. Click "Restore to this revision"
213
- 4. Space will rebuild from that commit
214
-
215
- ---
216
-
217
- ## Cost Considerations
218
-
219
- ### Hugging Face Spaces Pricing
220
-
221
- - **CPU Basic**: Free (with limitations)
222
- - **CPU Upgrade**: ~$0.03/hour
223
- - **GPU**: Not needed for this app
224
-
225
- ### External Services
226
-
227
- - **MongoDB Atlas**: Free tier (512 MB) to paid plans
228
- - **Google Cloud**: Free API quotas, then pay-per-use
229
- - **Discord**: Free
230
-
231
- ---
232
-
233
- ## Support & Troubleshooting
234
-
235
- ### Common Issues
236
-
237
- **"MongoDB Connection Failed"**
238
- - Verify MONGODB_URI in secrets
239
- - Check MongoDB Atlas network access (allow all IPs or Hugging Face IPs)
240
- - Verify MongoDB user permissions
241
-
242
- **"Google Sheets Connection Failed"**
243
- - Verify service account credentials
244
- - Check sheet permissions (share with service account email)
245
- - Ensure API is enabled in Google Cloud Console
246
-
247
- **"Go binary not found"**
248
- - Check Docker build logs
249
- - Verify Go files are being copied correctly
250
- - Ensure binaries are made executable
251
-
252
- ### Getting Help
253
-
254
- 1. Check Streamlit logs in the app
255
- 2. Review Hugging Face build logs
256
- 3. Test locally with same secrets
257
- 4. Contact development team
258
-
259
- ---
260
-
261
- ## Best Practices
262
-
263
- 1. **Version Control**: Keep all code in git
264
- 2. **Secret Management**: Never commit secrets
265
- 3. **Testing**: Test locally before deploying
266
- 4. **Monitoring**: Check logs regularly
267
- 5. **Backups**: Regular MongoDB backups
268
- 6. **Documentation**: Keep this guide updated
269
-
270
- ---
271
-
272
- ## Additional Resources
273
-
274
- - [Streamlit Documentation](https://docs.streamlit.io)
275
- - [Hugging Face Spaces Documentation](https://huggingface.co/docs/hub/spaces)
276
- - [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/)
277
- - [MongoDB Atlas Documentation](https://docs.atlas.mongodb.com/)
278
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -14,6 +14,8 @@ COPY func/dk_nhl_go ./func/dk_nhl_go
14
  COPY func/fd_nhl_go ./func/fd_nhl_go
15
  COPY func/dk_nba_go ./func/dk_nba_go
16
  COPY func/fd_nba_go ./func/fd_nba_go
 
 
17
  COPY func/showdown_go ./func/showdown_go
18
 
19
  # Build the Go programs for Linux
@@ -21,6 +23,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o dk_nhl_seed ./func/dk_nhl_go/NHL_seed_f
21
  RUN CGO_ENABLED=0 GOOS=linux go build -o fd_nhl_seed ./func/fd_nhl_go/NHL_seed_frames.go
22
  RUN CGO_ENABLED=0 GOOS=linux go build -o dk_nba_seed ./func/dk_nba_go/NBA_seed_frames.go
23
  RUN CGO_ENABLED=0 GOOS=linux go build -o fd_nba_seed ./func/fd_nba_go/NBA_seed_frames.go
 
 
24
  RUN CGO_ENABLED=0 GOOS=linux go build -o showdown_seed ./func/showdown_go/showdown_seed_frames.go
25
 
26
  # Python stage
@@ -47,11 +51,14 @@ COPY --from=go-builder /go-build/dk_nhl_seed ./dk_nhl_go/NHL_seed_frames
47
  COPY --from=go-builder /go-build/fd_nhl_seed ./fd_nhl_go/NHL_seed_frames
48
  COPY --from=go-builder /go-build/dk_nba_seed ./dk_nba_go/NBA_seed_frames
49
  COPY --from=go-builder /go-build/fd_nba_seed ./fd_nba_go/NBA_seed_frames
 
 
50
  COPY --from=go-builder /go-build/showdown_seed ./showdown_go/showdown_seed_frames
51
 
52
  # Make Go binaries executable
53
  RUN chmod +x ./dk_nhl_go/NHL_seed_frames ./fd_nhl_go/NHL_seed_frames
54
  RUN chmod +x ./dk_nba_go/NBA_seed_frames ./fd_nba_go/NBA_seed_frames
 
55
  RUN chmod +x ./showdown_go/showdown_seed_frames
56
 
57
  # Create .streamlit directory for config
 
14
  COPY func/fd_nhl_go ./func/fd_nhl_go
15
  COPY func/dk_nba_go ./func/dk_nba_go
16
  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
 
23
  RUN CGO_ENABLED=0 GOOS=linux go build -o fd_nhl_seed ./func/fd_nhl_go/NHL_seed_frames.go
24
  RUN CGO_ENABLED=0 GOOS=linux go build -o dk_nba_seed ./func/dk_nba_go/NBA_seed_frames.go
25
  RUN CGO_ENABLED=0 GOOS=linux go build -o fd_nba_seed ./func/fd_nba_go/NBA_seed_frames.go
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
 
51
  COPY --from=go-builder /go-build/fd_nhl_seed ./fd_nhl_go/NHL_seed_frames
52
  COPY --from=go-builder /go-build/dk_nba_seed ./dk_nba_go/NBA_seed_frames
53
  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
func/dk_nfl_go/NFL_seed_frames.go ADDED
@@ -0,0 +1,1131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ // Script Imports
5
+ "context"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io/ioutil"
9
+ "math/rand"
10
+ "os"
11
+ "slices"
12
+ "sort"
13
+ "strconv"
14
+ "strings"
15
+ "time"
16
+
17
+ // MongoDB Imports
18
+ "go.mongodb.org/mongo-driver/mongo"
19
+ "go.mongodb.org/mongo-driver/mongo/options"
20
+ )
21
+
22
+ type LineupData struct {
23
+ Salary []int32
24
+ Projection []float64
25
+ Team []string
26
+ Team_count []int32
27
+ Secondary []string
28
+ Secondary_count []int32
29
+ Ownership []float64
30
+ Players [][]int32
31
+ }
32
+
33
+ type Player struct {
34
+ ID int32 `json:"id"`
35
+ Name string `json:"name"`
36
+ Team string `json:"team"`
37
+ Position string `json:"position"`
38
+ Salary int32 `json:"salary"`
39
+ Projection float64 `json:"projection"`
40
+ Ownership float64 `json:"ownership"`
41
+ SalaryValue float64 `json:"salary_value"`
42
+ ProjValue float64 `json:"proj_value"`
43
+ OwnValue float64 `json:"own_value"`
44
+ SortValue float64 `json:"sort_value"`
45
+ Slate string `json:"slate"`
46
+ }
47
+
48
+ type PlayerSet struct {
49
+ Players []Player `json:"players"`
50
+ Maps struct {
51
+ NameMap map[string]string `json:"name_map"`
52
+ SalaryMap map[string]int32 `json:"salary_map"`
53
+ ProjectionMap map[string]float64 `json:"projection_map"`
54
+ OwnershipMap map[string]float64 `json:"ownership_map"`
55
+ TeamMap map[string]string `json:"team_map"`
56
+ } `json:"maps"`
57
+ }
58
+
59
+ type ProcessedData struct {
60
+ PlayersMedian PlayerSet `json:"players_median"`
61
+ }
62
+
63
+ type PlayerData struct {
64
+ Players []Player
65
+ NameMap map[int]string
66
+ }
67
+
68
+ type StrengthResult struct {
69
+ Index int
70
+ Data LineupData
71
+ Error error
72
+ }
73
+
74
+ type LineupDocument struct {
75
+ Salary int32 `bson:"salary"`
76
+ Projection float64 `bson:"proj"`
77
+ Team string `bson:"Team"`
78
+ Team_count int32 `bson:"Team_count"`
79
+ Secondary string `bson:"Secondary"`
80
+ Secondary_count int32 `bson:"Secondary_count"`
81
+ Ownership float64 `bson:"Own"`
82
+ QB int32 `bson:"QB"`
83
+ RB1 int32 `bson:"RB1"`
84
+ RB2 int32 `bson:"RB2"`
85
+ WR1 int32 `bson:"WR1"`
86
+ WR2 int32 `bson:"WR2"`
87
+ WR3 int32 `bson:"WR3"`
88
+ TE int32 `bson:"TE"`
89
+ FLEX int32 `bson:"FLEX"`
90
+ DST int32 `bson:"DST"`
91
+ CreatedAt time.Time `bson:"created_at"`
92
+ }
93
+
94
+ func loadPlayerData() (*ProcessedData, error) {
95
+ data, err := ioutil.ReadFile("dk_nfl_go/player_data.json")
96
+ if err != nil {
97
+ return nil, fmt.Errorf("failed to read in data: %v", err)
98
+ }
99
+
100
+ var processedData ProcessedData
101
+ if err := json.Unmarshal(data, &processedData); err != nil {
102
+ return nil, fmt.Errorf("failed to parse json: %v", err)
103
+ }
104
+
105
+ return &processedData, nil
106
+ }
107
+
108
+ func loadOptimals() (map[string]LineupData, error) {
109
+ data, err := ioutil.ReadFile("dk_nfl_go/optimal_lineups.json")
110
+ if err != nil {
111
+ return nil, fmt.Errorf("failed to parse optimals: %v", err)
112
+ }
113
+
114
+ type OptimalsJSON struct {
115
+ Slate string `json:"slate"`
116
+ Salary int32 `json:"salary"`
117
+ Projection float64 `json:"projection"`
118
+ Team string `json:"team"`
119
+ Team_count int32 `json:"team_count"`
120
+ Secondary string `json:"secondary"`
121
+ Secondary_count int32 `json:"secondary_count"`
122
+ Ownership float64 `json:"ownership"`
123
+ Players []int32 `json:"players"`
124
+ }
125
+
126
+ var allOptimals []OptimalsJSON
127
+ if err := json.Unmarshal(data, &allOptimals); err != nil {
128
+ return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
129
+ }
130
+
131
+ optimalsBySlate := make(map[string]LineupData)
132
+
133
+ for _, optimal := range allOptimals {
134
+ if _, exists := optimalsBySlate[optimal.Slate]; !exists {
135
+ optimalsBySlate[optimal.Slate] = LineupData{
136
+ Salary: []int32{},
137
+ Projection: []float64{},
138
+ Team: []string{},
139
+ Team_count: []int32{},
140
+ Secondary: []string{},
141
+ Secondary_count: []int32{},
142
+ Ownership: []float64{},
143
+ Players: [][]int32{},
144
+ }
145
+ }
146
+
147
+ slateData := optimalsBySlate[optimal.Slate]
148
+ slateData.Salary = append(slateData.Salary, optimal.Salary)
149
+ slateData.Projection = append(slateData.Projection, optimal.Projection)
150
+ slateData.Team = append(slateData.Team, optimal.Team)
151
+ slateData.Team_count = append(slateData.Team_count, optimal.Team_count)
152
+ slateData.Secondary = append(slateData.Secondary, optimal.Secondary)
153
+ slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count)
154
+ slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
155
+ slateData.Players = append(slateData.Players, optimal.Players)
156
+
157
+ optimalsBySlate[optimal.Slate] = slateData
158
+ }
159
+
160
+ return optimalsBySlate, nil
161
+ }
162
+
163
+ func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
164
+ if len(optimals.Salary) == 0 {
165
+ return results
166
+ }
167
+
168
+ // Simply append the optimal LineupData to existing results
169
+ return append(results, optimals)
170
+ }
171
+
172
+ func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
173
+ salaryMap := make(map[int32]int32)
174
+ projMap := make(map[int32]float64)
175
+ ownMap := make(map[int32]float64)
176
+ teamMap := make(map[int32]string)
177
+
178
+ for keyStr, value := range playerSet.Maps.SalaryMap {
179
+ key, err := strconv.Atoi(keyStr)
180
+ if err != nil {
181
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
182
+ continue
183
+ }
184
+ salaryMap[int32(key)] = value
185
+ }
186
+
187
+ for keyStr, value := range playerSet.Maps.ProjectionMap {
188
+ key, err := strconv.Atoi(keyStr)
189
+ if err != nil {
190
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
191
+ continue
192
+ }
193
+ projMap[int32(key)] = value
194
+ }
195
+
196
+ for keyStr, value := range playerSet.Maps.OwnershipMap {
197
+ key, err := strconv.Atoi(keyStr)
198
+ if err != nil {
199
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
200
+ continue
201
+ }
202
+ ownMap[int32(key)] = value
203
+ }
204
+
205
+ for keyStr, value := range playerSet.Maps.TeamMap {
206
+ key, err := strconv.Atoi(keyStr)
207
+ if err != nil {
208
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
209
+ continue
210
+ }
211
+ teamMap[int32(key)] = value
212
+ }
213
+
214
+ return salaryMap, projMap, ownMap, teamMap
215
+ }
216
+
217
+ func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
218
+ result := make([]U, len(input))
219
+
220
+ for i, key := range input {
221
+ if value, exists := valueMap[key]; exists {
222
+ result[i] = value
223
+ } else {
224
+ var zero U
225
+ result[i] = zero
226
+ }
227
+ }
228
+
229
+ return result
230
+ }
231
+
232
+ func sortChars(strData string) string {
233
+ runes := []rune(strData)
234
+ slices.Sort(runes)
235
+ return string(runes)
236
+ }
237
+
238
+ func rowMostCommon(row []int) (*int, *int) {
239
+ if len(row) == 0 {
240
+ return nil, nil
241
+ }
242
+
243
+ counts := make(map[int]int)
244
+ for _, value := range row {
245
+ counts[value]++
246
+ }
247
+
248
+ if len(counts) < 2 {
249
+ return nil, nil
250
+ }
251
+
252
+ mostCommon := 0
253
+ maxCount := 0
254
+ secondMost := 0
255
+ secondMax := 0
256
+
257
+ for value, count := range counts {
258
+ if count > maxCount {
259
+ secondMax = maxCount
260
+ secondMost = mostCommon
261
+
262
+ maxCount = count
263
+ mostCommon = value
264
+ } else if count > secondMax && count < maxCount {
265
+ secondMax = count
266
+ secondMost = value
267
+ }
268
+
269
+ }
270
+
271
+ return &mostCommon, &secondMost
272
+ }
273
+
274
+ func rowBiggestAndSecond(row []int) (int, int) {
275
+ if len(row) == 0 {
276
+ return 0, 0
277
+ }
278
+
279
+ counts := make(map[int]int)
280
+ for _, value := range row {
281
+ counts[value]++
282
+ }
283
+
284
+ if len(counts) == 1 {
285
+ return len(row), 0
286
+ }
287
+
288
+ biggestVal := 0
289
+ secondBiggestVal := 0
290
+
291
+ for _, count := range counts {
292
+ if count > biggestVal {
293
+ secondBiggestVal = biggestVal
294
+ biggestVal = count
295
+ } else if count > secondBiggestVal && count < biggestVal {
296
+ secondBiggestVal = count
297
+ }
298
+ }
299
+
300
+ return biggestVal, secondBiggestVal
301
+ }
302
+
303
+ func createOverallDFs(players []Player, pos string) PlayerData {
304
+ var filteredPlayers []Player
305
+ for _, player := range players {
306
+ if pos == "FLEX" {
307
+ if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") {
308
+ filteredPlayers = append(filteredPlayers, player)
309
+ }
310
+ } else {
311
+ if strings.Contains(player.Position, pos) {
312
+ filteredPlayers = append(filteredPlayers, player)
313
+ }
314
+ }
315
+ }
316
+
317
+ nameMap := make(map[int]string)
318
+ for i, player := range filteredPlayers {
319
+ nameMap[i] = player.Name
320
+ }
321
+
322
+ return PlayerData{
323
+ Players: filteredPlayers,
324
+ NameMap: nameMap,
325
+ }
326
+ }
327
+
328
+ func sumSalaryRows(data [][]int32) []int32 {
329
+ result := make([]int32, len(data))
330
+
331
+ for i, row := range data {
332
+ var sum int32
333
+ for _, value := range row {
334
+ sum += value
335
+ }
336
+ result[i] = sum
337
+ }
338
+
339
+ return result
340
+ }
341
+
342
+ func sumOwnRows(data [][]float64) []float64 {
343
+ result := make([]float64, len(data))
344
+
345
+ for i, row := range data {
346
+ var sum float64
347
+ for _, value := range row {
348
+ sum += value
349
+ }
350
+ result[i] = sum
351
+ }
352
+
353
+ return result
354
+ }
355
+
356
+ func sumProjRows(data [][]float64) []float64 {
357
+ result := make([]float64, len(data))
358
+
359
+ for i, row := range data {
360
+ var sum float64
361
+ for _, value := range row {
362
+ sum += value
363
+ }
364
+ result[i] = sum
365
+ }
366
+
367
+ return result
368
+ }
369
+
370
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
371
+ var validIndicies []int
372
+
373
+ for i, value := range values {
374
+ if value <= maxVal {
375
+ validIndicies = append(validIndicies, i)
376
+ }
377
+ }
378
+
379
+ return validIndicies
380
+ }
381
+
382
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
383
+ var validIndicies []int
384
+
385
+ for i, value := range values {
386
+ if value >= minVal {
387
+ validIndicies = append(validIndicies, i)
388
+ }
389
+ }
390
+
391
+ return validIndicies
392
+ }
393
+
394
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
395
+ result := make([]T, len(indicies))
396
+
397
+ for i, idx := range indicies {
398
+ result[i] = data[idx]
399
+ }
400
+
401
+ return result
402
+ }
403
+
404
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
405
+ indicies := make([]int, len(data.Ownership))
406
+ for i := range indicies {
407
+ indicies[i] = i
408
+ }
409
+
410
+ switch field {
411
+ case "salary":
412
+ sort.Slice(indicies, func(i, j int) bool {
413
+ if ascending {
414
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
415
+ }
416
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
417
+ })
418
+ case "projection":
419
+ sort.Slice(indicies, func(i, j int) bool {
420
+ if ascending {
421
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
422
+ }
423
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
424
+ })
425
+ case "ownership":
426
+ sort.Slice(indicies, func(i, j int) bool {
427
+ if ascending {
428
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
429
+ }
430
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
431
+ })
432
+ default:
433
+ sort.Slice(indicies, func(i, j int) bool {
434
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
435
+ })
436
+ }
437
+
438
+ return LineupData{
439
+ Salary: sliceByIndicies(data.Salary, indicies),
440
+ Projection: sliceByIndicies(data.Projection, indicies),
441
+ Team: sliceByIndicies(data.Team, indicies),
442
+ Team_count: sliceByIndicies(data.Team_count, indicies),
443
+ Secondary: sliceByIndicies(data.Secondary, indicies),
444
+ Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
445
+ Ownership: sliceByIndicies(data.Ownership, indicies),
446
+ Players: sliceByIndicies(data.Players, indicies),
447
+ }
448
+ }
449
+
450
+ func combineArrays(qb, rb1, rb2, wr1, wr2, wr3, te, flex, dst []int32) [][]int32 {
451
+ length := len(qb)
452
+
453
+ result := make([][]int32, length)
454
+
455
+ for i := 0; i < length; i++ {
456
+ result[i] = []int32{
457
+ qb[i],
458
+ rb1[i],
459
+ rb2[i],
460
+ wr1[i],
461
+ wr2[i],
462
+ wr3[i],
463
+ te[i],
464
+ flex[i],
465
+ dst[i],
466
+ }
467
+ }
468
+
469
+ return result
470
+ }
471
+
472
+ func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
473
+
474
+ salaries := make([][]int32, len(combinedArrays))
475
+ projections := make([][]float64, len(combinedArrays))
476
+ ownership := make([][]float64, len(combinedArrays))
477
+ teamArrays := make([][]string, len(combinedArrays))
478
+
479
+ for i, row := range combinedArrays {
480
+
481
+ players := row[0:9]
482
+
483
+ playerSalaries := processAndFill(players, salaryMap)
484
+ playerProjections := processAndFill(players, projMap)
485
+ playerOwnership := processAndFill(players, ownMap)
486
+ playerTeams := processAndFill(players, teamMap)
487
+
488
+ salaries[i] = playerSalaries
489
+ projections[i] = playerProjections
490
+ ownership[i] = playerOwnership
491
+ teamArrays[i] = playerTeams
492
+ }
493
+
494
+ totalSalaries := sumSalaryRows(salaries)
495
+ totalProjections := sumProjRows(projections)
496
+ totalOwnership := sumOwnRows(ownership)
497
+
498
+ teamData := make([]string, len(teamArrays))
499
+ teamCounts := make([]int32, len(teamArrays))
500
+ secondaryData := make([]string, len(teamArrays))
501
+ secondaryCounts := make([]int32, len(teamArrays))
502
+
503
+ for i, teams := range teamArrays {
504
+ teamMap := make(map[string]int)
505
+ uniqueTeams := make([]string, 0)
506
+ for _, team := range teams {
507
+ if _, exists := teamMap[team]; !exists {
508
+ teamMap[team] = len(uniqueTeams)
509
+ uniqueTeams = append(uniqueTeams, team)
510
+ }
511
+ }
512
+
513
+ teamInts := make([]int, len(teams))
514
+ for j, team := range teams {
515
+ teamInts[j] = teamMap[team]
516
+ }
517
+
518
+ mostCommon, secondMost := rowMostCommon(teamInts)
519
+ if mostCommon != nil {
520
+ teamData[i] = uniqueTeams[*mostCommon]
521
+ teamCount, _ := rowBiggestAndSecond(teamInts)
522
+ teamCounts[i] = int32(teamCount)
523
+ }
524
+ if secondMost != nil {
525
+ secondaryData[i] = uniqueTeams[*secondMost]
526
+ _, secondaryCount := rowBiggestAndSecond(teamInts)
527
+ secondaryCounts[i] = int32(secondaryCount)
528
+ }
529
+ }
530
+
531
+ var validIndicies []int
532
+ if site == "DK" {
533
+ validIndicies = filterMax(totalSalaries, int32(50000))
534
+ } else {
535
+ validIndicies = filterMax(totalSalaries, int32(60000))
536
+ }
537
+
538
+ validData := LineupData{
539
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
540
+ Projection: sliceByIndicies(totalProjections, validIndicies),
541
+ Team: sliceByIndicies(teamData, validIndicies),
542
+ Team_count: sliceByIndicies(teamCounts, validIndicies),
543
+ Secondary: sliceByIndicies(secondaryData, validIndicies),
544
+ Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
545
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
546
+ Players: sliceByIndicies(combinedArrays, validIndicies),
547
+ }
548
+
549
+ return sortDataByField(validData, "projection", false)
550
+ }
551
+
552
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
553
+ if len(values) == 0 {
554
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
555
+ }
556
+
557
+ if quantile < 0 || quantile > 1 {
558
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
559
+ }
560
+
561
+ sorted := make([]float64, len(values))
562
+ copy(sorted, values)
563
+ sort.Float64s(sorted)
564
+
565
+ index := int(float64(len(sorted)-1) * quantile)
566
+ return sorted[index], nil
567
+ }
568
+
569
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
570
+ if count > len(playerIDs) {
571
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
572
+ }
573
+
574
+ shuffled := make([]int32, len(playerIDs))
575
+ copy(shuffled, playerIDs)
576
+
577
+ for i := len(shuffled) - 1; i > 0; i-- {
578
+ j := rng.Intn(i + 1)
579
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
580
+ }
581
+
582
+ return shuffled[:count], nil
583
+ }
584
+
585
+ func generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
586
+
587
+ // DEBUG: Check pool sizes
588
+ fmt.Printf("DEBUG - Pool sizes: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d\n",
589
+ len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers))
590
+
591
+ if len(qbPlayers) == 0 || len(rbPlayers) == 0 || len(wrPlayers) == 0 || len(tePlayers) == 0 || len(flexPlayers) == 0 || len(dstPlayers) == 0 {
592
+ return nil, fmt.Errorf("one or more position pools is empty: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d",
593
+ len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers))
594
+ }
595
+
596
+ var validArrays [][]int32
597
+ attempts := 0
598
+ maxAttempts := numRows * 10
599
+
600
+ for len(validArrays) < numRows && attempts < maxAttempts {
601
+ attempts++
602
+
603
+ qb := qbPlayers[rng.Intn(len(qbPlayers))]
604
+ rb1 := rbPlayers[rng.Intn(len(rbPlayers))]
605
+ rb2 := rbPlayers[rng.Intn(len(rbPlayers))]
606
+ wr1 := wrPlayers[rng.Intn(len(wrPlayers))]
607
+ wr2 := wrPlayers[rng.Intn(len(wrPlayers))]
608
+ wr3 := wrPlayers[rng.Intn(len(wrPlayers))]
609
+ te := tePlayers[rng.Intn(len(tePlayers))]
610
+ flex := flexPlayers[rng.Intn(len(flexPlayers))]
611
+ dst := dstPlayers[rng.Intn(len(dstPlayers))]
612
+
613
+ if rb1.Name != rb2.Name && rb1.Name != flex.Name && rb2.Name != flex.Name && wr1.Name != wr2.Name && wr1.Name != wr3.Name && wr2.Name != wr3.Name &&
614
+ wr1.Name != flex.Name && wr2.Name != flex.Name && wr3.Name != flex.Name && te.Name != flex.Name {
615
+
616
+ playerIDs := []int32{qb.ID, rb1.ID, rb2.ID, wr1.ID, wr2.ID, wr3.ID, te.ID, flex.ID, dst.ID}
617
+ validArrays = append(validArrays, playerIDs)
618
+ }
619
+ }
620
+
621
+ if len(validArrays) == 0 {
622
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
623
+ }
624
+
625
+ return validArrays, nil
626
+ }
627
+
628
+ func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
629
+ if len(players) == 0 {
630
+ return nil, fmt.Errorf("no players provided")
631
+ }
632
+
633
+ var filteredPlayers []Player
634
+ for _, player := range players {
635
+ if pos == "FLEX" {
636
+ if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") {
637
+ filteredPlayers = append(filteredPlayers, player)
638
+ }
639
+ } else {
640
+ if strings.Contains(player.Position, pos) {
641
+ filteredPlayers = append(filteredPlayers, player)
642
+ }
643
+ }
644
+ }
645
+
646
+ ownVals := make([]float64, len(filteredPlayers))
647
+ for i, player := range filteredPlayers {
648
+ ownVals[i] = player.OwnValue
649
+ }
650
+
651
+ threshold, err := calculateQuantile(ownVals, strengthStep)
652
+ if err != nil {
653
+ return nil, err
654
+ }
655
+
656
+ var filtered []Player
657
+ for _, player := range filteredPlayers {
658
+ if player.OwnValue >= threshold {
659
+ filtered = append(filtered, player)
660
+ }
661
+ }
662
+
663
+ if len(filtered) == 0 {
664
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
665
+ }
666
+
667
+ return filtered, nil
668
+ }
669
+
670
+ func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) (LineupData, error) {
671
+
672
+ qbPlayers, err := filterPosPlayersInQuantile(players, "QB", strengthStep)
673
+ if err != nil {
674
+ return LineupData{}, fmt.Errorf("failed to filter QB players: %v", err)
675
+ }
676
+
677
+ rbPlayers, err := filterPosPlayersInQuantile(players, "RB", strengthStep)
678
+ if err != nil {
679
+ return LineupData{}, fmt.Errorf("failed to filter RB players: %v", err)
680
+ }
681
+
682
+ wrPlayers, err := filterPosPlayersInQuantile(players, "WR", strengthStep)
683
+ if err != nil {
684
+ return LineupData{}, fmt.Errorf("failed to filter WR players: %v", err)
685
+ }
686
+
687
+ tePlayers, err := filterPosPlayersInQuantile(players, "TE", strengthStep)
688
+ if err != nil {
689
+ return LineupData{}, fmt.Errorf("failed to filter TE players: %v", err)
690
+ }
691
+
692
+ flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep)
693
+ if err != nil {
694
+ return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err)
695
+ }
696
+
697
+ dstPlayers, err := filterPosPlayersInQuantile(players, "DST", strengthStep)
698
+ if err != nil {
699
+ return LineupData{}, fmt.Errorf("failed to filter DST players: %v", err)
700
+ }
701
+
702
+ overallArrays, err := generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers, numRows, strengthStep, rng)
703
+ if err != nil {
704
+ return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
705
+ }
706
+
707
+ result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
708
+
709
+ return result, nil
710
+ }
711
+
712
+ func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) ([]LineupData, error) {
713
+ resultsChan := make(chan StrengthResult, len(strengthVars))
714
+
715
+ for i, strengthStep := range strengthVars {
716
+ go func(step float64, rows int, index int) {
717
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
718
+
719
+ result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
720
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
721
+
722
+ if err != nil {
723
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
724
+ } else {
725
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
726
+ }
727
+ }(strengthStep, rowsPerLevel[i], i)
728
+ }
729
+
730
+ allResults := make([]LineupData, len(strengthVars))
731
+ var errors []error
732
+ successCount := 0
733
+
734
+ for i := 0; i < len(strengthVars); i++ {
735
+ result := <-resultsChan
736
+ if result.Error != nil {
737
+ errors = append(errors, result.Error)
738
+ } else {
739
+ allResults[result.Index] = result.Data
740
+ successCount++
741
+ }
742
+ }
743
+
744
+ if successCount == 0 {
745
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
746
+ }
747
+
748
+ var validResults []LineupData
749
+ for i, result := range allResults {
750
+ if len(result.Salary) > 0 {
751
+ validResults = append(validResults, result)
752
+ } else {
753
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
754
+ }
755
+ }
756
+
757
+ fmt.Printf("πŸ“Š Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
758
+ return validResults, nil
759
+ }
760
+
761
+ func printResults(results []LineupData, nameMap map[int32]string) {
762
+ fmt.Printf("Generated %d strength levels:\n", len(results))
763
+
764
+ // Combine all results into one big dataset
765
+ var allSalaries []int32
766
+ var allProjections []float64
767
+ var allOwnership []float64
768
+ var allPlayers [][]int32
769
+
770
+ for _, result := range results {
771
+ allSalaries = append(allSalaries, result.Salary...)
772
+ allProjections = append(allProjections, result.Projection...)
773
+ allOwnership = append(allOwnership, result.Ownership...)
774
+ allPlayers = append(allPlayers, result.Players...)
775
+ }
776
+
777
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
778
+
779
+ if len(allSalaries) > 0 {
780
+ // Print top 5 lineups (highest projection)
781
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
782
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
783
+ playerNames := []string{
784
+ getPlayerName(allPlayers[i][0], nameMap, "QB"), // QB
785
+ getPlayerName(allPlayers[i][1], nameMap, "RB1"), // RB1
786
+ getPlayerName(allPlayers[i][2], nameMap, "RB2"), // RB2
787
+ getPlayerName(allPlayers[i][3], nameMap, "WR1"), // WR1
788
+ getPlayerName(allPlayers[i][4], nameMap, "WR2"), // WR2
789
+ getPlayerName(allPlayers[i][5], nameMap, "WR3"), // WR3
790
+ getPlayerName(allPlayers[i][6], nameMap, "TE"), // TE
791
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"), // FLEX
792
+ getPlayerName(allPlayers[i][8], nameMap, "DST"), // DST
793
+ }
794
+
795
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
796
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
797
+ fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n",
798
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
799
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
800
+ }
801
+
802
+ // Print bottom 5 lineups (lowest projection)
803
+ if len(allSalaries) > 5 {
804
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
805
+ start := len(allSalaries) - 5
806
+ for i := start; i < len(allSalaries); i++ {
807
+ // Convert player IDs to names
808
+ playerNames := []string{
809
+ getPlayerName(allPlayers[i][0], nameMap, "QB"),
810
+ getPlayerName(allPlayers[i][1], nameMap, "RB1"),
811
+ getPlayerName(allPlayers[i][2], nameMap, "RB2"),
812
+ getPlayerName(allPlayers[i][3], nameMap, "WR1"),
813
+ getPlayerName(allPlayers[i][4], nameMap, "WR2"),
814
+ getPlayerName(allPlayers[i][5], nameMap, "WR3"),
815
+ getPlayerName(allPlayers[i][6], nameMap, "TE"),
816
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
817
+ getPlayerName(allPlayers[i][8], nameMap, "DST"),
818
+ }
819
+
820
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
821
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
822
+ fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n",
823
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
824
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
825
+ }
826
+ }
827
+ }
828
+ }
829
+
830
+ func removeDuplicates(results []LineupData) []LineupData {
831
+ seen := make(map[string]bool)
832
+ var uniqueLineups []LineupData
833
+
834
+ for _, result := range results {
835
+ for i := 0; i < len(result.Players); i++ {
836
+ // Create combo string like Python
837
+ combo := fmt.Sprintf("%d%d%d%d%d%d%d%d%d",
838
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
839
+ result.Players[i][3], result.Players[i][4], result.Players[i][5],
840
+ result.Players[i][6], result.Players[i][7], result.Players[i][8])
841
+
842
+ // Sort combo like Python
843
+ sortedCombo := sortChars(combo)
844
+
845
+ if !seen[sortedCombo] {
846
+ seen[sortedCombo] = true
847
+ uniqueLineups = append(uniqueLineups, LineupData{
848
+ Salary: []int32{result.Salary[i]},
849
+ Projection: []float64{result.Projection[i]},
850
+ Team: []string{result.Team[i]},
851
+ Team_count: []int32{result.Team_count[i]},
852
+ Secondary: []string{result.Secondary[i]},
853
+ Secondary_count: []int32{result.Secondary_count[i]},
854
+ Ownership: []float64{result.Ownership[i]},
855
+ Players: [][]int32{result.Players[i]},
856
+ })
857
+ }
858
+ }
859
+ }
860
+
861
+ if len(uniqueLineups) == 0 {
862
+ return []LineupData{}
863
+ }
864
+
865
+ var allSalary []int32
866
+ var allProjection []float64
867
+ var allTeam []string
868
+ var allTeamCount []int32
869
+ var allSecondary []string
870
+ var allSecondaryCount []int32
871
+ var allOwnership []float64
872
+ var allPlayers [][]int32
873
+
874
+ for _, lineup := range uniqueLineups {
875
+ allSalary = append(allSalary, lineup.Salary[0])
876
+ allProjection = append(allProjection, lineup.Projection[0])
877
+ allTeam = append(allTeam, lineup.Team[0])
878
+ allTeamCount = append(allTeamCount, lineup.Team_count[0])
879
+ allSecondary = append(allSecondary, lineup.Secondary[0])
880
+ allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
881
+ allOwnership = append(allOwnership, lineup.Ownership[0])
882
+ allPlayers = append(allPlayers, lineup.Players[0])
883
+ }
884
+
885
+ return []LineupData{{
886
+ Salary: allSalary,
887
+ Projection: allProjection,
888
+ Team: allTeam,
889
+ Team_count: allTeamCount,
890
+ Secondary: allSecondary,
891
+ Secondary_count: allSecondaryCount,
892
+ Ownership: allOwnership,
893
+ Players: allPlayers,
894
+ }}
895
+ }
896
+
897
+ func connectToMongoDB() (*mongo.Client, error) {
898
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
899
+
900
+ clientOptions := options.Client().
901
+ ApplyURI(uri).
902
+ SetRetryWrites(true).
903
+ SetServerSelectionTimeout(10 * time.Second).
904
+ SetMaxPoolSize(100).
905
+ SetMinPoolSize(10).
906
+ SetMaxConnIdleTime(30 * time.Second).
907
+ SetRetryReads(true)
908
+
909
+ client, err := mongo.Connect(context.TODO(), clientOptions)
910
+ if err != nil {
911
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
912
+ }
913
+
914
+ err = client.Ping(context.TODO(), nil)
915
+ if err != nil {
916
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
917
+ }
918
+
919
+ fmt.Printf("Connected to MongoDB!")
920
+ return client, nil
921
+ }
922
+
923
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
924
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
925
+
926
+ collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
927
+ collection := db.Collection(collectionName)
928
+
929
+ err := collection.Drop(context.TODO())
930
+ if err != nil {
931
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
932
+ }
933
+
934
+ var documents []interface{}
935
+
936
+ for _, result := range results {
937
+
938
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
939
+ fmt.Printf("Warning: Empty result found, skipping\n")
940
+ continue
941
+ }
942
+
943
+ for i := 0; i < len(result.Salary); i++ {
944
+ if len(result.Players[i]) < 9 {
945
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 9\n", i, len(result.Players[i]))
946
+ }
947
+
948
+ doc := LineupDocument{
949
+ Salary: result.Salary[i],
950
+ Projection: result.Projection[i],
951
+ Team: result.Team[i],
952
+ Team_count: result.Team_count[i],
953
+ Secondary: result.Secondary[i],
954
+ Secondary_count: result.Secondary_count[i],
955
+ Ownership: result.Ownership[i],
956
+ QB: result.Players[i][0],
957
+ RB1: result.Players[i][1],
958
+ RB2: result.Players[i][2],
959
+ WR1: result.Players[i][3],
960
+ WR2: result.Players[i][4],
961
+ WR3: result.Players[i][5],
962
+ TE: result.Players[i][6],
963
+ FLEX: result.Players[i][7],
964
+ DST: result.Players[i][8],
965
+ CreatedAt: time.Now(),
966
+ }
967
+
968
+ documents = append(documents, doc)
969
+ }
970
+ }
971
+
972
+ if len(documents) == 0 {
973
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
974
+ }
975
+
976
+ if len(documents) > 500000 {
977
+ documents = documents[:500000]
978
+ }
979
+
980
+ chunkSize := 250000
981
+ for i := 0; i < len(documents); i += chunkSize {
982
+ end := i + chunkSize
983
+ if end > len(documents) {
984
+ end = len(documents)
985
+ }
986
+
987
+ chunk := documents[i:end]
988
+
989
+ for attempt := 0; attempt < 5; attempt++ {
990
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
991
+
992
+ opts := options.InsertMany().SetOrdered(false)
993
+ _, err := collection.InsertMany(ctx, chunk, opts)
994
+ cancel()
995
+
996
+ if err == nil {
997
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
998
+ break
999
+ }
1000
+
1001
+ fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
1002
+ if attempt < 4 {
1003
+ time.Sleep(1 * time.Second)
1004
+ }
1005
+ }
1006
+
1007
+ if err != nil {
1008
+ return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
1009
+ }
1010
+ }
1011
+
1012
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
1013
+ return nil
1014
+ }
1015
+
1016
+ func groupPlayersBySlate(players []Player) map[string][]Player {
1017
+ slateGroups := make(map[string][]Player)
1018
+
1019
+ for _, player := range players {
1020
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
1021
+ }
1022
+
1023
+ return slateGroups
1024
+ }
1025
+
1026
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
1027
+ if name, exists := nameMap[playerID]; exists && name != "" {
1028
+ return name
1029
+ }
1030
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
1031
+ }
1032
+
1033
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
1034
+ nameMap := make(map[int32]string)
1035
+
1036
+ for keyStr, value := range playerSet.Maps.NameMap {
1037
+ key, err := strconv.Atoi(keyStr)
1038
+ if err != nil {
1039
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
1040
+ continue
1041
+ }
1042
+ nameMap[int32(key)] = value
1043
+ }
1044
+
1045
+ return nameMap
1046
+ }
1047
+
1048
+ func main() {
1049
+ site := "DK"
1050
+ sport := "NFL"
1051
+ if len(os.Args) > 1 {
1052
+ site = os.Args[1]
1053
+ }
1054
+ if len(os.Args) > 2 {
1055
+ sport = os.Args[2]
1056
+ }
1057
+ processedData, err := loadPlayerData()
1058
+ if err != nil {
1059
+ fmt.Printf("Error loading data: %v\n", err)
1060
+ return
1061
+ }
1062
+
1063
+ start := time.Now()
1064
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
1065
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
1066
+
1067
+ SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
1068
+
1069
+ salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
1070
+
1071
+ nameMap := convertNamesToMaps(&processedData.PlayersMedian)
1072
+
1073
+ mongoClient, err := connectToMongoDB()
1074
+ if err != nil {
1075
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
1076
+ return
1077
+ }
1078
+ defer func() {
1079
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
1080
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
1081
+ }
1082
+ }()
1083
+
1084
+ optimalsBySlate, err := loadOptimals()
1085
+ if err != nil {
1086
+ fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
1087
+ optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
1088
+ } else {
1089
+ totalOptimals := 0
1090
+ for _, optimals := range optimalsBySlate {
1091
+ totalOptimals += len(optimals.Salary)
1092
+ }
1093
+ fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
1094
+ }
1095
+
1096
+ for slate, players := range SlateGroups {
1097
+
1098
+ fmt.Printf("Processing slate: %s\n", slate)
1099
+
1100
+ results, err := runSeedframeRoutines(
1101
+ players, strengthVars, rowsPerLevel,
1102
+ salaryMapJSON, projectionMapJSON,
1103
+ ownershipMapJSON, teamMapJSON, site)
1104
+
1105
+ if err != nil {
1106
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
1107
+ continue
1108
+ }
1109
+
1110
+ // Get optimal lineups for this specific slate
1111
+ slateOptimals := optimalsBySlate[slate]
1112
+
1113
+ // Append optimal lineups for this slate
1114
+ finalResults := appendOptimalLineups(results, slateOptimals)
1115
+
1116
+ exportResults := removeDuplicates(finalResults)
1117
+
1118
+ exportResults[0] = sortDataByField(exportResults[0], "projection", false)
1119
+
1120
+ err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
1121
+ if err != nil {
1122
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
1123
+ continue
1124
+ }
1125
+
1126
+ printResults(exportResults, nameMap)
1127
+ }
1128
+
1129
+ // Add this line at the end
1130
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
1131
+ }
func/fd_nfl_go/NFL_seed_frames.go ADDED
@@ -0,0 +1,1131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ // Script Imports
5
+ "context"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io/ioutil"
9
+ "math/rand"
10
+ "os"
11
+ "slices"
12
+ "sort"
13
+ "strconv"
14
+ "strings"
15
+ "time"
16
+
17
+ // MongoDB Imports
18
+ "go.mongodb.org/mongo-driver/mongo"
19
+ "go.mongodb.org/mongo-driver/mongo/options"
20
+ )
21
+
22
+ type LineupData struct {
23
+ Salary []int32
24
+ Projection []float64
25
+ Team []string
26
+ Team_count []int32
27
+ Secondary []string
28
+ Secondary_count []int32
29
+ Ownership []float64
30
+ Players [][]int32
31
+ }
32
+
33
+ type Player struct {
34
+ ID int32 `json:"id"`
35
+ Name string `json:"name"`
36
+ Team string `json:"team"`
37
+ Position string `json:"position"`
38
+ Salary int32 `json:"salary"`
39
+ Projection float64 `json:"projection"`
40
+ Ownership float64 `json:"ownership"`
41
+ SalaryValue float64 `json:"salary_value"`
42
+ ProjValue float64 `json:"proj_value"`
43
+ OwnValue float64 `json:"own_value"`
44
+ SortValue float64 `json:"sort_value"`
45
+ Slate string `json:"slate"`
46
+ }
47
+
48
+ type PlayerSet struct {
49
+ Players []Player `json:"players"`
50
+ Maps struct {
51
+ NameMap map[string]string `json:"name_map"`
52
+ SalaryMap map[string]int32 `json:"salary_map"`
53
+ ProjectionMap map[string]float64 `json:"projection_map"`
54
+ OwnershipMap map[string]float64 `json:"ownership_map"`
55
+ TeamMap map[string]string `json:"team_map"`
56
+ } `json:"maps"`
57
+ }
58
+
59
+ type ProcessedData struct {
60
+ PlayersMedian PlayerSet `json:"players_median"`
61
+ }
62
+
63
+ type PlayerData struct {
64
+ Players []Player
65
+ NameMap map[int]string
66
+ }
67
+
68
+ type StrengthResult struct {
69
+ Index int
70
+ Data LineupData
71
+ Error error
72
+ }
73
+
74
+ type LineupDocument struct {
75
+ Salary int32 `bson:"salary"`
76
+ Projection float64 `bson:"proj"`
77
+ Team string `bson:"Team"`
78
+ Team_count int32 `bson:"Team_count"`
79
+ Secondary string `bson:"Secondary"`
80
+ Secondary_count int32 `bson:"Secondary_count"`
81
+ Ownership float64 `bson:"Own"`
82
+ QB int32 `bson:"QB"`
83
+ RB1 int32 `bson:"RB1"`
84
+ RB2 int32 `bson:"RB2"`
85
+ WR1 int32 `bson:"WR1"`
86
+ WR2 int32 `bson:"WR2"`
87
+ WR3 int32 `bson:"WR3"`
88
+ TE int32 `bson:"TE"`
89
+ FLEX int32 `bson:"FLEX"`
90
+ DST int32 `bson:"DST"`
91
+ CreatedAt time.Time `bson:"created_at"`
92
+ }
93
+
94
+ func loadPlayerData() (*ProcessedData, error) {
95
+ data, err := ioutil.ReadFile("fd_nfl_go/player_data.json")
96
+ if err != nil {
97
+ return nil, fmt.Errorf("failed to read in data: %v", err)
98
+ }
99
+
100
+ var processedData ProcessedData
101
+ if err := json.Unmarshal(data, &processedData); err != nil {
102
+ return nil, fmt.Errorf("failed to parse json: %v", err)
103
+ }
104
+
105
+ return &processedData, nil
106
+ }
107
+
108
+ func loadOptimals() (map[string]LineupData, error) {
109
+ data, err := ioutil.ReadFile("fd_nfl_go/optimal_lineups.json")
110
+ if err != nil {
111
+ return nil, fmt.Errorf("failed to parse optimals: %v", err)
112
+ }
113
+
114
+ type OptimalsJSON struct {
115
+ Slate string `json:"slate"`
116
+ Salary int32 `json:"salary"`
117
+ Projection float64 `json:"projection"`
118
+ Team string `json:"team"`
119
+ Team_count int32 `json:"team_count"`
120
+ Secondary string `json:"secondary"`
121
+ Secondary_count int32 `json:"secondary_count"`
122
+ Ownership float64 `json:"ownership"`
123
+ Players []int32 `json:"players"`
124
+ }
125
+
126
+ var allOptimals []OptimalsJSON
127
+ if err := json.Unmarshal(data, &allOptimals); err != nil {
128
+ return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
129
+ }
130
+
131
+ optimalsBySlate := make(map[string]LineupData)
132
+
133
+ for _, optimal := range allOptimals {
134
+ if _, exists := optimalsBySlate[optimal.Slate]; !exists {
135
+ optimalsBySlate[optimal.Slate] = LineupData{
136
+ Salary: []int32{},
137
+ Projection: []float64{},
138
+ Team: []string{},
139
+ Team_count: []int32{},
140
+ Secondary: []string{},
141
+ Secondary_count: []int32{},
142
+ Ownership: []float64{},
143
+ Players: [][]int32{},
144
+ }
145
+ }
146
+
147
+ slateData := optimalsBySlate[optimal.Slate]
148
+ slateData.Salary = append(slateData.Salary, optimal.Salary)
149
+ slateData.Projection = append(slateData.Projection, optimal.Projection)
150
+ slateData.Team = append(slateData.Team, optimal.Team)
151
+ slateData.Team_count = append(slateData.Team_count, optimal.Team_count)
152
+ slateData.Secondary = append(slateData.Secondary, optimal.Secondary)
153
+ slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count)
154
+ slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
155
+ slateData.Players = append(slateData.Players, optimal.Players)
156
+
157
+ optimalsBySlate[optimal.Slate] = slateData
158
+ }
159
+
160
+ return optimalsBySlate, nil
161
+ }
162
+
163
+ func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
164
+ if len(optimals.Salary) == 0 {
165
+ return results
166
+ }
167
+
168
+ // Simply append the optimal LineupData to existing results
169
+ return append(results, optimals)
170
+ }
171
+
172
+ func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
173
+ salaryMap := make(map[int32]int32)
174
+ projMap := make(map[int32]float64)
175
+ ownMap := make(map[int32]float64)
176
+ teamMap := make(map[int32]string)
177
+
178
+ for keyStr, value := range playerSet.Maps.SalaryMap {
179
+ key, err := strconv.Atoi(keyStr)
180
+ if err != nil {
181
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
182
+ continue
183
+ }
184
+ salaryMap[int32(key)] = value
185
+ }
186
+
187
+ for keyStr, value := range playerSet.Maps.ProjectionMap {
188
+ key, err := strconv.Atoi(keyStr)
189
+ if err != nil {
190
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
191
+ continue
192
+ }
193
+ projMap[int32(key)] = value
194
+ }
195
+
196
+ for keyStr, value := range playerSet.Maps.OwnershipMap {
197
+ key, err := strconv.Atoi(keyStr)
198
+ if err != nil {
199
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
200
+ continue
201
+ }
202
+ ownMap[int32(key)] = value
203
+ }
204
+
205
+ for keyStr, value := range playerSet.Maps.TeamMap {
206
+ key, err := strconv.Atoi(keyStr)
207
+ if err != nil {
208
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
209
+ continue
210
+ }
211
+ teamMap[int32(key)] = value
212
+ }
213
+
214
+ return salaryMap, projMap, ownMap, teamMap
215
+ }
216
+
217
+ func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
218
+ result := make([]U, len(input))
219
+
220
+ for i, key := range input {
221
+ if value, exists := valueMap[key]; exists {
222
+ result[i] = value
223
+ } else {
224
+ var zero U
225
+ result[i] = zero
226
+ }
227
+ }
228
+
229
+ return result
230
+ }
231
+
232
+ func sortChars(strData string) string {
233
+ runes := []rune(strData)
234
+ slices.Sort(runes)
235
+ return string(runes)
236
+ }
237
+
238
+ func rowMostCommon(row []int) (*int, *int) {
239
+ if len(row) == 0 {
240
+ return nil, nil
241
+ }
242
+
243
+ counts := make(map[int]int)
244
+ for _, value := range row {
245
+ counts[value]++
246
+ }
247
+
248
+ if len(counts) < 2 {
249
+ return nil, nil
250
+ }
251
+
252
+ mostCommon := 0
253
+ maxCount := 0
254
+ secondMost := 0
255
+ secondMax := 0
256
+
257
+ for value, count := range counts {
258
+ if count > maxCount {
259
+ secondMax = maxCount
260
+ secondMost = mostCommon
261
+
262
+ maxCount = count
263
+ mostCommon = value
264
+ } else if count > secondMax && count < maxCount {
265
+ secondMax = count
266
+ secondMost = value
267
+ }
268
+
269
+ }
270
+
271
+ return &mostCommon, &secondMost
272
+ }
273
+
274
+ func rowBiggestAndSecond(row []int) (int, int) {
275
+ if len(row) == 0 {
276
+ return 0, 0
277
+ }
278
+
279
+ counts := make(map[int]int)
280
+ for _, value := range row {
281
+ counts[value]++
282
+ }
283
+
284
+ if len(counts) == 1 {
285
+ return len(row), 0
286
+ }
287
+
288
+ biggestVal := 0
289
+ secondBiggestVal := 0
290
+
291
+ for _, count := range counts {
292
+ if count > biggestVal {
293
+ secondBiggestVal = biggestVal
294
+ biggestVal = count
295
+ } else if count > secondBiggestVal && count < biggestVal {
296
+ secondBiggestVal = count
297
+ }
298
+ }
299
+
300
+ return biggestVal, secondBiggestVal
301
+ }
302
+
303
+ func createOverallDFs(players []Player, pos string) PlayerData {
304
+ var filteredPlayers []Player
305
+ for _, player := range players {
306
+ if pos == "FLEX" {
307
+ if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") {
308
+ filteredPlayers = append(filteredPlayers, player)
309
+ }
310
+ } else {
311
+ if strings.Contains(player.Position, pos) {
312
+ filteredPlayers = append(filteredPlayers, player)
313
+ }
314
+ }
315
+ }
316
+
317
+ nameMap := make(map[int]string)
318
+ for i, player := range filteredPlayers {
319
+ nameMap[i] = player.Name
320
+ }
321
+
322
+ return PlayerData{
323
+ Players: filteredPlayers,
324
+ NameMap: nameMap,
325
+ }
326
+ }
327
+
328
+ func sumSalaryRows(data [][]int32) []int32 {
329
+ result := make([]int32, len(data))
330
+
331
+ for i, row := range data {
332
+ var sum int32
333
+ for _, value := range row {
334
+ sum += value
335
+ }
336
+ result[i] = sum
337
+ }
338
+
339
+ return result
340
+ }
341
+
342
+ func sumOwnRows(data [][]float64) []float64 {
343
+ result := make([]float64, len(data))
344
+
345
+ for i, row := range data {
346
+ var sum float64
347
+ for _, value := range row {
348
+ sum += value
349
+ }
350
+ result[i] = sum
351
+ }
352
+
353
+ return result
354
+ }
355
+
356
+ func sumProjRows(data [][]float64) []float64 {
357
+ result := make([]float64, len(data))
358
+
359
+ for i, row := range data {
360
+ var sum float64
361
+ for _, value := range row {
362
+ sum += value
363
+ }
364
+ result[i] = sum
365
+ }
366
+
367
+ return result
368
+ }
369
+
370
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
371
+ var validIndicies []int
372
+
373
+ for i, value := range values {
374
+ if value <= maxVal {
375
+ validIndicies = append(validIndicies, i)
376
+ }
377
+ }
378
+
379
+ return validIndicies
380
+ }
381
+
382
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
383
+ var validIndicies []int
384
+
385
+ for i, value := range values {
386
+ if value >= minVal {
387
+ validIndicies = append(validIndicies, i)
388
+ }
389
+ }
390
+
391
+ return validIndicies
392
+ }
393
+
394
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
395
+ result := make([]T, len(indicies))
396
+
397
+ for i, idx := range indicies {
398
+ result[i] = data[idx]
399
+ }
400
+
401
+ return result
402
+ }
403
+
404
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
405
+ indicies := make([]int, len(data.Ownership))
406
+ for i := range indicies {
407
+ indicies[i] = i
408
+ }
409
+
410
+ switch field {
411
+ case "salary":
412
+ sort.Slice(indicies, func(i, j int) bool {
413
+ if ascending {
414
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
415
+ }
416
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
417
+ })
418
+ case "projection":
419
+ sort.Slice(indicies, func(i, j int) bool {
420
+ if ascending {
421
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
422
+ }
423
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
424
+ })
425
+ case "ownership":
426
+ sort.Slice(indicies, func(i, j int) bool {
427
+ if ascending {
428
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
429
+ }
430
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
431
+ })
432
+ default:
433
+ sort.Slice(indicies, func(i, j int) bool {
434
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
435
+ })
436
+ }
437
+
438
+ return LineupData{
439
+ Salary: sliceByIndicies(data.Salary, indicies),
440
+ Projection: sliceByIndicies(data.Projection, indicies),
441
+ Team: sliceByIndicies(data.Team, indicies),
442
+ Team_count: sliceByIndicies(data.Team_count, indicies),
443
+ Secondary: sliceByIndicies(data.Secondary, indicies),
444
+ Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
445
+ Ownership: sliceByIndicies(data.Ownership, indicies),
446
+ Players: sliceByIndicies(data.Players, indicies),
447
+ }
448
+ }
449
+
450
+ func combineArrays(qb, rb1, rb2, wr1, wr2, wr3, te, flex, dst []int32) [][]int32 {
451
+ length := len(qb)
452
+
453
+ result := make([][]int32, length)
454
+
455
+ for i := 0; i < length; i++ {
456
+ result[i] = []int32{
457
+ qb[i],
458
+ rb1[i],
459
+ rb2[i],
460
+ wr1[i],
461
+ wr2[i],
462
+ wr3[i],
463
+ te[i],
464
+ flex[i],
465
+ dst[i],
466
+ }
467
+ }
468
+
469
+ return result
470
+ }
471
+
472
+ func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
473
+
474
+ salaries := make([][]int32, len(combinedArrays))
475
+ projections := make([][]float64, len(combinedArrays))
476
+ ownership := make([][]float64, len(combinedArrays))
477
+ teamArrays := make([][]string, len(combinedArrays))
478
+
479
+ for i, row := range combinedArrays {
480
+
481
+ players := row[0:9]
482
+
483
+ playerSalaries := processAndFill(players, salaryMap)
484
+ playerProjections := processAndFill(players, projMap)
485
+ playerOwnership := processAndFill(players, ownMap)
486
+ playerTeams := processAndFill(players, teamMap)
487
+
488
+ salaries[i] = playerSalaries
489
+ projections[i] = playerProjections
490
+ ownership[i] = playerOwnership
491
+ teamArrays[i] = playerTeams
492
+ }
493
+
494
+ totalSalaries := sumSalaryRows(salaries)
495
+ totalProjections := sumProjRows(projections)
496
+ totalOwnership := sumOwnRows(ownership)
497
+
498
+ teamData := make([]string, len(teamArrays))
499
+ teamCounts := make([]int32, len(teamArrays))
500
+ secondaryData := make([]string, len(teamArrays))
501
+ secondaryCounts := make([]int32, len(teamArrays))
502
+
503
+ for i, teams := range teamArrays {
504
+ teamMap := make(map[string]int)
505
+ uniqueTeams := make([]string, 0)
506
+ for _, team := range teams {
507
+ if _, exists := teamMap[team]; !exists {
508
+ teamMap[team] = len(uniqueTeams)
509
+ uniqueTeams = append(uniqueTeams, team)
510
+ }
511
+ }
512
+
513
+ teamInts := make([]int, len(teams))
514
+ for j, team := range teams {
515
+ teamInts[j] = teamMap[team]
516
+ }
517
+
518
+ mostCommon, secondMost := rowMostCommon(teamInts)
519
+ if mostCommon != nil {
520
+ teamData[i] = uniqueTeams[*mostCommon]
521
+ teamCount, _ := rowBiggestAndSecond(teamInts)
522
+ teamCounts[i] = int32(teamCount)
523
+ }
524
+ if secondMost != nil {
525
+ secondaryData[i] = uniqueTeams[*secondMost]
526
+ _, secondaryCount := rowBiggestAndSecond(teamInts)
527
+ secondaryCounts[i] = int32(secondaryCount)
528
+ }
529
+ }
530
+
531
+ var validIndicies []int
532
+ if site == "DK" {
533
+ validIndicies = filterMax(totalSalaries, int32(50000))
534
+ } else {
535
+ validIndicies = filterMax(totalSalaries, int32(60000))
536
+ }
537
+
538
+ validData := LineupData{
539
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
540
+ Projection: sliceByIndicies(totalProjections, validIndicies),
541
+ Team: sliceByIndicies(teamData, validIndicies),
542
+ Team_count: sliceByIndicies(teamCounts, validIndicies),
543
+ Secondary: sliceByIndicies(secondaryData, validIndicies),
544
+ Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
545
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
546
+ Players: sliceByIndicies(combinedArrays, validIndicies),
547
+ }
548
+
549
+ return sortDataByField(validData, "projection", false)
550
+ }
551
+
552
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
553
+ if len(values) == 0 {
554
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
555
+ }
556
+
557
+ if quantile < 0 || quantile > 1 {
558
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
559
+ }
560
+
561
+ sorted := make([]float64, len(values))
562
+ copy(sorted, values)
563
+ sort.Float64s(sorted)
564
+
565
+ index := int(float64(len(sorted)-1) * quantile)
566
+ return sorted[index], nil
567
+ }
568
+
569
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
570
+ if count > len(playerIDs) {
571
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
572
+ }
573
+
574
+ shuffled := make([]int32, len(playerIDs))
575
+ copy(shuffled, playerIDs)
576
+
577
+ for i := len(shuffled) - 1; i > 0; i-- {
578
+ j := rng.Intn(i + 1)
579
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
580
+ }
581
+
582
+ return shuffled[:count], nil
583
+ }
584
+
585
+ func generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
586
+
587
+ // DEBUG: Check pool sizes
588
+ fmt.Printf("DEBUG - Pool sizes: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d\n",
589
+ len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers))
590
+
591
+ if len(qbPlayers) == 0 || len(rbPlayers) == 0 || len(wrPlayers) == 0 || len(tePlayers) == 0 || len(flexPlayers) == 0 || len(dstPlayers) == 0 {
592
+ return nil, fmt.Errorf("one or more position pools is empty: QB=%d, RB=%d, WR=%d, TE=%d, FLEX=%d, DST=%d",
593
+ len(qbPlayers), len(rbPlayers), len(wrPlayers), len(tePlayers), len(flexPlayers), len(dstPlayers))
594
+ }
595
+
596
+ var validArrays [][]int32
597
+ attempts := 0
598
+ maxAttempts := numRows * 10
599
+
600
+ for len(validArrays) < numRows && attempts < maxAttempts {
601
+ attempts++
602
+
603
+ qb := qbPlayers[rng.Intn(len(qbPlayers))]
604
+ rb1 := rbPlayers[rng.Intn(len(rbPlayers))]
605
+ rb2 := rbPlayers[rng.Intn(len(rbPlayers))]
606
+ wr1 := wrPlayers[rng.Intn(len(wrPlayers))]
607
+ wr2 := wrPlayers[rng.Intn(len(wrPlayers))]
608
+ wr3 := wrPlayers[rng.Intn(len(wrPlayers))]
609
+ te := tePlayers[rng.Intn(len(tePlayers))]
610
+ flex := flexPlayers[rng.Intn(len(flexPlayers))]
611
+ dst := dstPlayers[rng.Intn(len(dstPlayers))]
612
+
613
+ if rb1.Name != rb2.Name && rb1.Name != flex.Name && rb2.Name != flex.Name && wr1.Name != wr2.Name && wr1.Name != wr3.Name && wr2.Name != wr3.Name &&
614
+ wr1.Name != flex.Name && wr2.Name != flex.Name && wr3.Name != flex.Name && te.Name != flex.Name {
615
+
616
+ playerIDs := []int32{qb.ID, rb1.ID, rb2.ID, wr1.ID, wr2.ID, wr3.ID, te.ID, flex.ID, dst.ID}
617
+ validArrays = append(validArrays, playerIDs)
618
+ }
619
+ }
620
+
621
+ if len(validArrays) == 0 {
622
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
623
+ }
624
+
625
+ return validArrays, nil
626
+ }
627
+
628
+ func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
629
+ if len(players) == 0 {
630
+ return nil, fmt.Errorf("no players provided")
631
+ }
632
+
633
+ var filteredPlayers []Player
634
+ for _, player := range players {
635
+ if pos == "FLEX" {
636
+ if !strings.Contains(player.Position, "QB") && !strings.Contains(player.Position, "DST") {
637
+ filteredPlayers = append(filteredPlayers, player)
638
+ }
639
+ } else {
640
+ if strings.Contains(player.Position, pos) {
641
+ filteredPlayers = append(filteredPlayers, player)
642
+ }
643
+ }
644
+ }
645
+
646
+ ownVals := make([]float64, len(filteredPlayers))
647
+ for i, player := range filteredPlayers {
648
+ ownVals[i] = player.OwnValue
649
+ }
650
+
651
+ threshold, err := calculateQuantile(ownVals, strengthStep)
652
+ if err != nil {
653
+ return nil, err
654
+ }
655
+
656
+ var filtered []Player
657
+ for _, player := range filteredPlayers {
658
+ if player.OwnValue >= threshold {
659
+ filtered = append(filtered, player)
660
+ }
661
+ }
662
+
663
+ if len(filtered) == 0 {
664
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
665
+ }
666
+
667
+ return filtered, nil
668
+ }
669
+
670
+ func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) (LineupData, error) {
671
+
672
+ qbPlayers, err := filterPosPlayersInQuantile(players, "QB", strengthStep)
673
+ if err != nil {
674
+ return LineupData{}, fmt.Errorf("failed to filter QB players: %v", err)
675
+ }
676
+
677
+ rbPlayers, err := filterPosPlayersInQuantile(players, "RB", strengthStep)
678
+ if err != nil {
679
+ return LineupData{}, fmt.Errorf("failed to filter RB players: %v", err)
680
+ }
681
+
682
+ wrPlayers, err := filterPosPlayersInQuantile(players, "WR", strengthStep)
683
+ if err != nil {
684
+ return LineupData{}, fmt.Errorf("failed to filter WR players: %v", err)
685
+ }
686
+
687
+ tePlayers, err := filterPosPlayersInQuantile(players, "TE", strengthStep)
688
+ if err != nil {
689
+ return LineupData{}, fmt.Errorf("failed to filter TE players: %v", err)
690
+ }
691
+
692
+ flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep)
693
+ if err != nil {
694
+ return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err)
695
+ }
696
+
697
+ dstPlayers, err := filterPosPlayersInQuantile(players, "DST", strengthStep)
698
+ if err != nil {
699
+ return LineupData{}, fmt.Errorf("failed to filter DST players: %v", err)
700
+ }
701
+
702
+ overallArrays, err := generateBaseArrays(qbPlayers, rbPlayers, wrPlayers, tePlayers, flexPlayers, dstPlayers, numRows, strengthStep, rng)
703
+ if err != nil {
704
+ return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
705
+ }
706
+
707
+ result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
708
+
709
+ return result, nil
710
+ }
711
+
712
+ func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) ([]LineupData, error) {
713
+ resultsChan := make(chan StrengthResult, len(strengthVars))
714
+
715
+ for i, strengthStep := range strengthVars {
716
+ go func(step float64, rows int, index int) {
717
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
718
+
719
+ result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
720
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
721
+
722
+ if err != nil {
723
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
724
+ } else {
725
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
726
+ }
727
+ }(strengthStep, rowsPerLevel[i], i)
728
+ }
729
+
730
+ allResults := make([]LineupData, len(strengthVars))
731
+ var errors []error
732
+ successCount := 0
733
+
734
+ for i := 0; i < len(strengthVars); i++ {
735
+ result := <-resultsChan
736
+ if result.Error != nil {
737
+ errors = append(errors, result.Error)
738
+ } else {
739
+ allResults[result.Index] = result.Data
740
+ successCount++
741
+ }
742
+ }
743
+
744
+ if successCount == 0 {
745
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
746
+ }
747
+
748
+ var validResults []LineupData
749
+ for i, result := range allResults {
750
+ if len(result.Salary) > 0 {
751
+ validResults = append(validResults, result)
752
+ } else {
753
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
754
+ }
755
+ }
756
+
757
+ fmt.Printf("πŸ“Š Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
758
+ return validResults, nil
759
+ }
760
+
761
+ func printResults(results []LineupData, nameMap map[int32]string) {
762
+ fmt.Printf("Generated %d strength levels:\n", len(results))
763
+
764
+ // Combine all results into one big dataset
765
+ var allSalaries []int32
766
+ var allProjections []float64
767
+ var allOwnership []float64
768
+ var allPlayers [][]int32
769
+
770
+ for _, result := range results {
771
+ allSalaries = append(allSalaries, result.Salary...)
772
+ allProjections = append(allProjections, result.Projection...)
773
+ allOwnership = append(allOwnership, result.Ownership...)
774
+ allPlayers = append(allPlayers, result.Players...)
775
+ }
776
+
777
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
778
+
779
+ if len(allSalaries) > 0 {
780
+ // Print top 5 lineups (highest projection)
781
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
782
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
783
+ playerNames := []string{
784
+ getPlayerName(allPlayers[i][0], nameMap, "QB"), // QB
785
+ getPlayerName(allPlayers[i][1], nameMap, "RB1"), // RB1
786
+ getPlayerName(allPlayers[i][2], nameMap, "RB2"), // RB2
787
+ getPlayerName(allPlayers[i][3], nameMap, "WR1"), // WR1
788
+ getPlayerName(allPlayers[i][4], nameMap, "WR2"), // WR2
789
+ getPlayerName(allPlayers[i][5], nameMap, "WR3"), // WR3
790
+ getPlayerName(allPlayers[i][6], nameMap, "TE"), // TE
791
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"), // FLEX
792
+ getPlayerName(allPlayers[i][8], nameMap, "DST"), // DST
793
+ }
794
+
795
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
796
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
797
+ fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n",
798
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
799
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
800
+ }
801
+
802
+ // Print bottom 5 lineups (lowest projection)
803
+ if len(allSalaries) > 5 {
804
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
805
+ start := len(allSalaries) - 5
806
+ for i := start; i < len(allSalaries); i++ {
807
+ // Convert player IDs to names
808
+ playerNames := []string{
809
+ getPlayerName(allPlayers[i][0], nameMap, "QB"),
810
+ getPlayerName(allPlayers[i][1], nameMap, "RB1"),
811
+ getPlayerName(allPlayers[i][2], nameMap, "RB2"),
812
+ getPlayerName(allPlayers[i][3], nameMap, "WR1"),
813
+ getPlayerName(allPlayers[i][4], nameMap, "WR2"),
814
+ getPlayerName(allPlayers[i][5], nameMap, "WR3"),
815
+ getPlayerName(allPlayers[i][6], nameMap, "TE"),
816
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
817
+ getPlayerName(allPlayers[i][8], nameMap, "DST"),
818
+ }
819
+
820
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
821
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
822
+ fmt.Printf(" Players: QB=%s, RB1=%s, RB2=%s, WR1=%s, WR2=%s, WR3=%s, TE=%s, FLEX=%s, DST=%s\n",
823
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
824
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
825
+ }
826
+ }
827
+ }
828
+ }
829
+
830
+ func removeDuplicates(results []LineupData) []LineupData {
831
+ seen := make(map[string]bool)
832
+ var uniqueLineups []LineupData
833
+
834
+ for _, result := range results {
835
+ for i := 0; i < len(result.Players); i++ {
836
+ // Create combo string like Python
837
+ combo := fmt.Sprintf("%d%d%d%d%d%d%d%d%d",
838
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
839
+ result.Players[i][3], result.Players[i][4], result.Players[i][5],
840
+ result.Players[i][6], result.Players[i][7], result.Players[i][8])
841
+
842
+ // Sort combo like Python
843
+ sortedCombo := sortChars(combo)
844
+
845
+ if !seen[sortedCombo] {
846
+ seen[sortedCombo] = true
847
+ uniqueLineups = append(uniqueLineups, LineupData{
848
+ Salary: []int32{result.Salary[i]},
849
+ Projection: []float64{result.Projection[i]},
850
+ Team: []string{result.Team[i]},
851
+ Team_count: []int32{result.Team_count[i]},
852
+ Secondary: []string{result.Secondary[i]},
853
+ Secondary_count: []int32{result.Secondary_count[i]},
854
+ Ownership: []float64{result.Ownership[i]},
855
+ Players: [][]int32{result.Players[i]},
856
+ })
857
+ }
858
+ }
859
+ }
860
+
861
+ if len(uniqueLineups) == 0 {
862
+ return []LineupData{}
863
+ }
864
+
865
+ var allSalary []int32
866
+ var allProjection []float64
867
+ var allTeam []string
868
+ var allTeamCount []int32
869
+ var allSecondary []string
870
+ var allSecondaryCount []int32
871
+ var allOwnership []float64
872
+ var allPlayers [][]int32
873
+
874
+ for _, lineup := range uniqueLineups {
875
+ allSalary = append(allSalary, lineup.Salary[0])
876
+ allProjection = append(allProjection, lineup.Projection[0])
877
+ allTeam = append(allTeam, lineup.Team[0])
878
+ allTeamCount = append(allTeamCount, lineup.Team_count[0])
879
+ allSecondary = append(allSecondary, lineup.Secondary[0])
880
+ allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
881
+ allOwnership = append(allOwnership, lineup.Ownership[0])
882
+ allPlayers = append(allPlayers, lineup.Players[0])
883
+ }
884
+
885
+ return []LineupData{{
886
+ Salary: allSalary,
887
+ Projection: allProjection,
888
+ Team: allTeam,
889
+ Team_count: allTeamCount,
890
+ Secondary: allSecondary,
891
+ Secondary_count: allSecondaryCount,
892
+ Ownership: allOwnership,
893
+ Players: allPlayers,
894
+ }}
895
+ }
896
+
897
+ func connectToMongoDB() (*mongo.Client, error) {
898
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
899
+
900
+ clientOptions := options.Client().
901
+ ApplyURI(uri).
902
+ SetRetryWrites(true).
903
+ SetServerSelectionTimeout(10 * time.Second).
904
+ SetMaxPoolSize(100).
905
+ SetMinPoolSize(10).
906
+ SetMaxConnIdleTime(30 * time.Second).
907
+ SetRetryReads(true)
908
+
909
+ client, err := mongo.Connect(context.TODO(), clientOptions)
910
+ if err != nil {
911
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
912
+ }
913
+
914
+ err = client.Ping(context.TODO(), nil)
915
+ if err != nil {
916
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
917
+ }
918
+
919
+ fmt.Printf("Connected to MongoDB!")
920
+ return client, nil
921
+ }
922
+
923
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
924
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
925
+
926
+ collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
927
+ collection := db.Collection(collectionName)
928
+
929
+ err := collection.Drop(context.TODO())
930
+ if err != nil {
931
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
932
+ }
933
+
934
+ var documents []interface{}
935
+
936
+ for _, result := range results {
937
+
938
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
939
+ fmt.Printf("Warning: Empty result found, skipping\n")
940
+ continue
941
+ }
942
+
943
+ for i := 0; i < len(result.Salary); i++ {
944
+ if len(result.Players[i]) < 9 {
945
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 9\n", i, len(result.Players[i]))
946
+ }
947
+
948
+ doc := LineupDocument{
949
+ Salary: result.Salary[i],
950
+ Projection: result.Projection[i],
951
+ Team: result.Team[i],
952
+ Team_count: result.Team_count[i],
953
+ Secondary: result.Secondary[i],
954
+ Secondary_count: result.Secondary_count[i],
955
+ Ownership: result.Ownership[i],
956
+ QB: result.Players[i][0],
957
+ RB1: result.Players[i][1],
958
+ RB2: result.Players[i][2],
959
+ WR1: result.Players[i][3],
960
+ WR2: result.Players[i][4],
961
+ WR3: result.Players[i][5],
962
+ TE: result.Players[i][6],
963
+ FLEX: result.Players[i][7],
964
+ DST: result.Players[i][8],
965
+ CreatedAt: time.Now(),
966
+ }
967
+
968
+ documents = append(documents, doc)
969
+ }
970
+ }
971
+
972
+ if len(documents) == 0 {
973
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
974
+ }
975
+
976
+ if len(documents) > 500000 {
977
+ documents = documents[:500000]
978
+ }
979
+
980
+ chunkSize := 250000
981
+ for i := 0; i < len(documents); i += chunkSize {
982
+ end := i + chunkSize
983
+ if end > len(documents) {
984
+ end = len(documents)
985
+ }
986
+
987
+ chunk := documents[i:end]
988
+
989
+ for attempt := 0; attempt < 5; attempt++ {
990
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
991
+
992
+ opts := options.InsertMany().SetOrdered(false)
993
+ _, err := collection.InsertMany(ctx, chunk, opts)
994
+ cancel()
995
+
996
+ if err == nil {
997
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
998
+ break
999
+ }
1000
+
1001
+ fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
1002
+ if attempt < 4 {
1003
+ time.Sleep(1 * time.Second)
1004
+ }
1005
+ }
1006
+
1007
+ if err != nil {
1008
+ return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
1009
+ }
1010
+ }
1011
+
1012
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
1013
+ return nil
1014
+ }
1015
+
1016
+ func groupPlayersBySlate(players []Player) map[string][]Player {
1017
+ slateGroups := make(map[string][]Player)
1018
+
1019
+ for _, player := range players {
1020
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
1021
+ }
1022
+
1023
+ return slateGroups
1024
+ }
1025
+
1026
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
1027
+ if name, exists := nameMap[playerID]; exists && name != "" {
1028
+ return name
1029
+ }
1030
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
1031
+ }
1032
+
1033
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
1034
+ nameMap := make(map[int32]string)
1035
+
1036
+ for keyStr, value := range playerSet.Maps.NameMap {
1037
+ key, err := strconv.Atoi(keyStr)
1038
+ if err != nil {
1039
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
1040
+ continue
1041
+ }
1042
+ nameMap[int32(key)] = value
1043
+ }
1044
+
1045
+ return nameMap
1046
+ }
1047
+
1048
+ func main() {
1049
+ site := "DK"
1050
+ sport := "NFL"
1051
+ if len(os.Args) > 1 {
1052
+ site = os.Args[1]
1053
+ }
1054
+ if len(os.Args) > 2 {
1055
+ sport = os.Args[2]
1056
+ }
1057
+ processedData, err := loadPlayerData()
1058
+ if err != nil {
1059
+ fmt.Printf("Error loading data: %v\n", err)
1060
+ return
1061
+ }
1062
+
1063
+ start := time.Now()
1064
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
1065
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
1066
+
1067
+ SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
1068
+
1069
+ salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
1070
+
1071
+ nameMap := convertNamesToMaps(&processedData.PlayersMedian)
1072
+
1073
+ mongoClient, err := connectToMongoDB()
1074
+ if err != nil {
1075
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
1076
+ return
1077
+ }
1078
+ defer func() {
1079
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
1080
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
1081
+ }
1082
+ }()
1083
+
1084
+ optimalsBySlate, err := loadOptimals()
1085
+ if err != nil {
1086
+ fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
1087
+ optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
1088
+ } else {
1089
+ totalOptimals := 0
1090
+ for _, optimals := range optimalsBySlate {
1091
+ totalOptimals += len(optimals.Salary)
1092
+ }
1093
+ fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
1094
+ }
1095
+
1096
+ for slate, players := range SlateGroups {
1097
+
1098
+ fmt.Printf("Processing slate: %s\n", slate)
1099
+
1100
+ results, err := runSeedframeRoutines(
1101
+ players, strengthVars, rowsPerLevel,
1102
+ salaryMapJSON, projectionMapJSON,
1103
+ ownershipMapJSON, teamMapJSON, site)
1104
+
1105
+ if err != nil {
1106
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
1107
+ continue
1108
+ }
1109
+
1110
+ // Get optimal lineups for this specific slate
1111
+ slateOptimals := optimalsBySlate[slate]
1112
+
1113
+ // Append optimal lineups for this slate
1114
+ finalResults := appendOptimalLineups(results, slateOptimals)
1115
+
1116
+ exportResults := removeDuplicates(finalResults)
1117
+
1118
+ exportResults[0] = sortDataByField(exportResults[0], "projection", false)
1119
+
1120
+ err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
1121
+ if err != nil {
1122
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
1123
+ continue
1124
+ }
1125
+
1126
+ printResults(exportResults, nameMap)
1127
+ }
1128
+
1129
+ // Add this line at the end
1130
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
1131
+ }
src/database.py CHANGED
@@ -52,12 +52,14 @@ uri = get_secret("MONGODB_URI", "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcl
52
  client = MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=10000, w=0)
53
 
54
  nhl_db = client['NHL_Database']
 
55
  nba_db = client['NBA_Database']
56
  contest_db = client["Contest_Information"]
57
 
58
  # Google Sheets URL
59
- NHL_Master_hold = get_secret("MASTER_SHEET_URL", 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863')
60
  NBA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-bD6_u9pqrYE6Yrlbb_wEkmH-ot0/edit?gid=172632260#gid=172632260'
 
61
 
62
  # Discord Webhook
63
  discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
 
52
  client = MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=10000, w=0)
53
 
54
  nhl_db = client['NHL_Database']
55
+ nfl_db = client['NFL_Database']
56
  nba_db = client['NBA_Database']
57
  contest_db = client["Contest_Information"]
58
 
59
  # Google Sheets URL
60
+ NHL_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863'
61
  NBA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-bD6_u9pqrYE6Yrlbb_wEkmH-ot0/edit?gid=172632260#gid=172632260'
62
+ NFL_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1I_1Ve3F4tftgfLQQoRKOJ351XfEG48s36OxXUKxmgS8/edit#gid=1668460741'
63
 
64
  # Discord Webhook
65
  discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
src/sports/nba_functions.py CHANGED
@@ -758,7 +758,6 @@ def trending_script():
758
  worksheet.batch_clear(['A:S'])
759
  worksheet.update([Trending_df.columns.values.tolist()] + Trending_df.values.tolist())
760
 
761
-
762
  def DK_NBA_ROO_Build(dk_roo_player_hold):
763
  total_sims = 1000
764
  wrong_name = ['Lu Dort', 'Carlton Carrington']
@@ -2333,30 +2332,6 @@ def upload_sd_dfs_data(client, sd_roo_final):
2333
  except Exception as e:
2334
  st.write(f"Retry due to error: {e}")
2335
  time_sleep(1)
2336
-
2337
- # try:
2338
- # sh = gc.open_by_url(NBA_Master_hold)
2339
- # worksheet = sh.worksheet('ROO_Backlog')
2340
- # Overall_Proj = DataFrame(worksheet.get_all_records())
2341
-
2342
- # except:
2343
- # sh = gc2.open_by_url(NBA_Master_hold)
2344
- # worksheet = sh.worksheet('ROO_Backlog')
2345
- # Overall_Proj = DataFrame(worksheet.get_all_records())
2346
-
2347
- # collection = db['Range_Of_Outcomes_Backlog']
2348
- # Overall_Proj = Overall_Proj.reset_index(drop=True)
2349
- # chunk_size = 100000
2350
- # collection.drop()
2351
- # for i in range(0, len(Overall_Proj), chunk_size):
2352
- # for _ in range(5):
2353
- # try:
2354
- # df_chunk = Overall_Proj.iloc[i:i + chunk_size]
2355
- # collection.insert_many(df_chunk.to_dict('records'), ordered=False)
2356
- # break
2357
- # except Exception as e:
2358
- # st.write(f"Retry due to error: {e}")
2359
- # time_sleep(1)
2360
 
2361
  def upload_dfs_data(client, roo_final):
2362
 
 
758
  worksheet.batch_clear(['A:S'])
759
  worksheet.update([Trending_df.columns.values.tolist()] + Trending_df.values.tolist())
760
 
 
761
  def DK_NBA_ROO_Build(dk_roo_player_hold):
762
  total_sims = 1000
763
  wrong_name = ['Lu Dort', 'Carlton Carrington']
 
2332
  except Exception as e:
2333
  st.write(f"Retry due to error: {e}")
2334
  time_sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2335
 
2336
  def upload_dfs_data(client, roo_final):
2337
 
src/sports/nfl_functions.py CHANGED
The diff for this file is too large to render. See raw diff
 
src/streamlit_app.py CHANGED
@@ -269,9 +269,236 @@ if selected_tab == "NHL Updates":
269
 
270
  if selected_tab == "NFL Updates":
271
  from sports.nfl_functions import *
272
- st.info("NFL updates coming soon!")
273
- st.write("NFL functionality will be added later on.")
274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  if selected_tab == "NBA Updates":
276
  from sports.nba_functions import *
277
 
 
269
 
270
  if selected_tab == "NFL Updates":
271
  from sports.nfl_functions import *
 
 
272
 
273
+ if st.button(f"οΏ½ Update NFL models and generate seed frames", type="primary", use_container_width=True):
274
+ x: int = 1
275
+ high_end: int = 1
276
+
277
+ while x <= high_end:
278
+ Prop_Data_Creation_var = 0
279
+ DK_Team_Level_Stacks_var = 0
280
+ FD_Team_Level_Stacks_var = 0
281
+ DK_ROO_Structure_Creation_var = 0
282
+ FD_ROO_Structure_Creation_var = 0
283
+ DK_seed_frame_var = 0
284
+ FD_seed_frame_var = 0
285
+ upload_betting_var = 0
286
+ DK_SD_ROO_var = 0
287
+ FD_SD_ROO_var = 0
288
+ DK_SD_seed_frame_var = 0
289
+ FD_SD_seed_frame_var = 0
290
+
291
+
292
+ if upload_betting_var == 0:
293
+ upload_betting_data(client)
294
+ upload_betting_var = 1
295
+
296
+ time_sleep(1)
297
+
298
+ if DK_Team_Level_Stacks_var == 0:
299
+ Overall_Proj = DK_Team_Level_Stacks(dk_stacks_hold, dk_raw)
300
+ DK_Team_Level_Stacks_var = 1
301
+
302
+ if DK_Team_Level_Stacks_var == 1:
303
+
304
+ collection = nfl_db['DK_DFS_Stacks']
305
+ Overall_Proj = Overall_Proj.reset_index(drop=True)
306
+ chunk_size = 100000
307
+ collection.drop()
308
+ for i in range(0, len(Overall_Proj), chunk_size):
309
+ for _ in range(5):
310
+ try:
311
+ df_chunk = Overall_Proj.iloc[i:i + chunk_size]
312
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
313
+ break
314
+ except Exception as e:
315
+ print(f"Retry due to error: {e}")
316
+ time_sleep(1)
317
+ else:
318
+ pass
319
+
320
+ time_sleep(1)
321
+
322
+ if FD_Team_Level_Stacks_var == 0:
323
+ Overall_Proj = FD_Team_Level_Stacks(fd_stacks_hold, fd_raw)
324
+ FD_Team_Level_Stacks_var = 1
325
+
326
+ if FD_Team_Level_Stacks_var == 1:
327
+
328
+ collection = nfl_db['FD_DFS_Stacks']
329
+ Overall_Proj = Overall_Proj.reset_index(drop=True)
330
+ chunk_size = 100000
331
+ collection.drop()
332
+ for i in range(0, len(Overall_Proj), chunk_size):
333
+ for _ in range(5):
334
+ try:
335
+ df_chunk = Overall_Proj.iloc[i:i + chunk_size]
336
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
337
+ break
338
+ except Exception as e:
339
+ print(f"Retry due to error: {e}")
340
+ time_sleep(1)
341
+ else:
342
+ pass
343
+
344
+ time_sleep(1)
345
+
346
+ if DK_ROO_Structure_Creation_var == 0:
347
+ DK_ROO = DK_ROO_Structure_Creation(dk_player_hold, short_team_acro, long_team_acro, team_only_acro)
348
+ DK_ROO_Structure_Creation_var = 1
349
+
350
+ if DK_ROO_Structure_Creation_var == 1:
351
+
352
+ collection = nfl_db['DK_NFL_ROO']
353
+ DK_ROO = DK_ROO.reset_index(drop=True)
354
+ chunk_size = 100000
355
+ collection.drop()
356
+ for i in range(0, len(DK_ROO), chunk_size):
357
+ for _ in range(5):
358
+ try:
359
+ df_chunk = DK_ROO.iloc[i:i + chunk_size]
360
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
361
+ break
362
+ except Exception as e:
363
+ print(f"Retry due to error: {e}")
364
+ time_sleep(1)
365
+
366
+ else:
367
+ pass
368
+
369
+ time_sleep(1)
370
+
371
+ if FD_ROO_Structure_Creation_var == 0:
372
+ FD_ROO = FD_ROO_Structure_Creation(fd_player_hold, short_team_acro, long_team_acro, team_only_acro)
373
+ FD_ROO_Structure_Creation_var = 1
374
+
375
+ if FD_ROO_Structure_Creation_var == 1:
376
+
377
+ collection = nfl_db['FD_NFL_ROO']
378
+ FD_ROO = FD_ROO.reset_index(drop=True)
379
+ chunk_size = 100000
380
+ collection.drop()
381
+ for i in range(0, len(FD_ROO), chunk_size):
382
+ for _ in range(5):
383
+ try:
384
+ df_chunk = FD_ROO.iloc[i:i + chunk_size]
385
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
386
+ break
387
+ except Exception as e:
388
+ print(f"Retry due to error: {e}")
389
+ time_sleep(1)
390
+ else:
391
+ pass
392
+
393
+ time_sleep(1)
394
+
395
+ if DK_seed_frame_var == 0:
396
+ DK_seed_frame(DK_ROO, seed_team_acro, short_team_acro, client)
397
+ DK_seed_frame_var = 1
398
+
399
+ time_sleep(1)
400
+
401
+ if FD_seed_frame_var == 0:
402
+ FD_seed_frame(FD_ROO, seed_team_acro, short_team_acro, client)
403
+ FD_seed_frame_var = 1
404
+
405
+ time_sleep(1)
406
+
407
+ if Prop_Data_Creation_var == 0:
408
+ Overall_Proj = Prop_Data_Creation()
409
+ Prop_Data_Creation_var = 1
410
+
411
+ if Prop_Data_Creation_var == 1:
412
+
413
+ collection = nfl_db['Player_Baselines']
414
+ Overall_Proj = Overall_Proj.reset_index(drop=True)
415
+ chunk_size = 100000
416
+ collection.drop()
417
+ for i in range(0, len(Overall_Proj), chunk_size):
418
+ for _ in range(5):
419
+ try:
420
+ df_chunk = Overall_Proj.iloc[i:i + chunk_size]
421
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
422
+ break
423
+ except Exception as e:
424
+ print(f"Retry due to error: {e}")
425
+ time_sleep(1)
426
+ else:
427
+ pass
428
+
429
+ time_sleep(1)
430
+
431
+ if DK_SD_ROO_var == 0:
432
+ dk_sd_roo_result = DK_SD_ROO(dk_showdown_hold, team_only_acro, short_team_acro, long_team_acro, dk_sd_projections)
433
+ DK_SD_ROO_var = 1
434
+
435
+ if DK_SD_ROO_var == 1:
436
+
437
+ collection = nfl_db['DK_SD_NFL_ROO']
438
+ dk_sd_roo_result = dk_sd_roo_result.reset_index(drop=True)
439
+ chunk_size = 100000
440
+ collection.drop()
441
+ for i in range(0, len(dk_sd_roo_result), chunk_size):
442
+ for _ in range(5):
443
+ try:
444
+ df_chunk = dk_sd_roo_result.iloc[i:i + chunk_size]
445
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
446
+ break
447
+ except Exception as e:
448
+ print(f"Retry due to error: {e}")
449
+ time_sleep(1)
450
+
451
+ else:
452
+ pass
453
+
454
+ time_sleep(1)
455
+
456
+ if FD_SD_ROO_var == 0:
457
+ fd_sd_roo_result = FD_SD_ROO(fd_showdown_hold, team_only_acro, short_team_acro, long_team_acro, fd_sd_projections)
458
+ FD_SD_ROO_var = 1
459
+
460
+ if FD_SD_ROO_var == 1:
461
+
462
+ collection = nfl_db['FD_SD_NFL_ROO']
463
+ fd_sd_roo_result = fd_sd_roo_result.reset_index(drop=True)
464
+ chunk_size = 100000
465
+ collection.drop()
466
+ for i in range(0, len(fd_sd_roo_result), chunk_size):
467
+ for _ in range(5):
468
+ try:
469
+ df_chunk = fd_sd_roo_result.iloc[i:i + chunk_size]
470
+ collection.insert_many(df_chunk.to_dict('records'), ordered=False)
471
+ break
472
+ except Exception as e:
473
+ print(f"Retry due to error: {e}")
474
+ time_sleep(1)
475
+ else:
476
+ pass
477
+
478
+ time_sleep(1)
479
+
480
+ if DK_SD_seed_frame_var == 0:
481
+ DK_SD_seed_frame(dk_sd_roo_result, team_only_acro, long_team_acro, client, dk_showdown_options, dk_sd_projections)
482
+ DK_SD_seed_frame_var = 1
483
+
484
+ time_sleep(1)
485
+
486
+ if FD_SD_seed_frame_var == 0:
487
+ FD_SD_seed_frame(fd_sd_roo_result, team_only_acro, long_team_acro, client, fd_showdown_options, fd_sd_projections)
488
+ FD_SD_seed_frame_var = 1
489
+
490
+ time_sleep(1)
491
+
492
+ print (f'currently on run {x}, {high_end - x} runs remaining')
493
+
494
+ x += 1
495
+
496
+ if high_end > 1:
497
+ time_sleep(600)
498
+
499
+ st.success("βœ… NFL updates completed successfully!")
500
+ st.balloons()
501
+
502
  if selected_tab == "NBA Updates":
503
  from sports.nba_functions import *
504