James McCool
commited on
Commit
Β·
85eb84f
1
Parent(s):
9c0a74e
Adding NFL functionality
Browse files- CHANGES.md +0 -303
- DEPLOYMENT.md +0 -278
- Dockerfile +7 -0
- func/dk_nfl_go/NFL_seed_frames.go +1131 -0
- func/fd_nfl_go/NFL_seed_frames.go +1131 -0
- src/database.py +3 -1
- src/sports/nba_functions.py +0 -25
- src/sports/nfl_functions.py +0 -0
- src/streamlit_app.py +229 -2
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 =
|
| 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 |
|