James McCool commited on
Commit
28226be
·
1 Parent(s): 938de6b

Lots of additions and improvements to the NHL update functions and addition of the NBA functions

Browse files
func/NBA_own_regress.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymongo
2
+ import pandas as pd
3
+ import numpy as np
4
+ import xgboost as xgb
5
+ import lightgbm as lgb
6
+
7
+ from sklearn.model_selection import train_test_split
8
+ from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
9
+ from sklearn.svm import SVR
10
+ from sklearn.neighbors import KNeighborsRegressor
11
+ from sklearn.linear_model import LinearRegression
12
+
13
+ from src.database import *
14
+
15
+ collection = contest_db["NBA_reg_exposure_frames"]
16
+ cursor = collection.find()
17
+ raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
18
+ raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
19
+ raw_display = raw_display[raw_display['Actual'].between(1, 100)]
20
+
21
+ print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
22
+
23
+ collection = nba_db["Player_Range_Of_Outcomes"]
24
+ cursor = collection.find()
25
+ raw_projections = pd.DataFrame(list(cursor))
26
+ try:
27
+ raw_projections = raw_projections[['Player', 'Minutes Proj', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '4x%', '5x%', '6x%', 'GPP%',
28
+ 'Own', 'Small_Own', 'Large_Own', 'Cash_Own', 'CPT_Own', 'LevX', 'ValX', 'site', 'version', 'slate', 'timestamp', 'player_ID']]
29
+ except:
30
+ raw_projections = raw_projections[['Player', 'Minutes Proj', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '4x%', '5x%', '6x%', 'GPP%',
31
+ 'Own', 'Small_Own', 'Large_Own', 'Cash_Own', 'CPT_Own', 'LevX', 'ValX', 'site', 'version', 'slate', 'timestamp', 'player_id']]
32
+ raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
33
+ raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
34
+
35
+ current_projections = raw_projections[(raw_projections['slate'] == 'Main Slate') & (raw_projections['site'] == 'Draftkings')]
36
+
37
+ intcols = ['Contest ID', 'Salary']
38
+ floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
39
+ percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
40
+ stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
41
+
42
+ for col in intcols:
43
+ raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
44
+ for col in floatcols:
45
+ raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
46
+ for col in percentagecols:
47
+ raw_display[col] = raw_display[col] * 100.0
48
+ for col in stringcols:
49
+ raw_display[col] = raw_display[col].astype(str)
50
+
51
+ df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
52
+
53
+ df_clean['Actual'] = df_clean['Actual'] * .90
54
+ df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
55
+ df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
56
+ df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
57
+ df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
58
+ df_clean['base_ownership'] = 800.0 / df_clean['contest_size']
59
+ df_clean['value_play'] = np.where((df_clean['Salary'] <= 4000) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 6.0), 1, 0)
60
+ df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
61
+ df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 6.0), 1, 0)
62
+ df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 5.0), 1, 0)
63
+ df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
64
+ lambda x: x / x.sum() * 800
65
+ )
66
+
67
+ # Prepare features and target
68
+ feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
69
+ X = df_clean[feature_cols]
70
+ y = df_clean['ownership_share']
71
+
72
+ # Train-test split
73
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
74
+
75
+ # Create and train model
76
+ xgb_model = xgb.XGBRegressor(
77
+ n_estimators=1000,
78
+ learning_rate=0.10,
79
+ max_depth=5,
80
+ random_state=42,
81
+ base_score=10
82
+ )
83
+
84
+ xgb_model.fit(X_train, y_train)
85
+
86
+ lgb_model = lgb.LGBMRegressor(
87
+ n_estimators=1000, # number of boosting rounds
88
+ learning_rate=0.1, # learning rate
89
+ num_leaves=31, # max number of leaves in one tree
90
+ random_state=42,
91
+ verbose=-1 # suppress warnings during training
92
+ )
93
+
94
+ lgb_model.fit(X_train, y_train / 100)
95
+
96
+ knn_model = KNeighborsRegressor(
97
+ n_neighbors=5,
98
+ weights='distance' # or 'uniform'
99
+ )
100
+
101
+ knn_model.fit(X_train, y_train)
102
+
103
+ __all__ = ['xgb_model', 'lgb_model', 'knn_model']
104
+
105
+ if __name__ == '__main__':
106
+ X_full = df_clean[feature_cols]
107
+ y_full = df_clean['Exposure Overall']
108
+
109
+ # Get predictions from all your models on the full dataset
110
+ y_pred_xgb_full = np.clip(xgb_model.predict(X_full), 0, 100)
111
+ y_pred_lgb_full = np.clip(lgb_model.predict(X_full), 0, 100) * 100
112
+ y_pred_knn_full = np.clip(knn_model.predict(X_full), 0, 100)
113
+
114
+ # Create combo prediction
115
+ y_pred_combo_full = (y_pred_xgb_full + y_pred_lgb_full + y_pred_knn_full) / 3
116
+
117
+ # Create full comparison DataFrame
118
+ comparison_full = pd.DataFrame({
119
+ 'Actual_Exposure': y_full.values,
120
+ 'XGB': y_pred_xgb_full,
121
+ 'LGB': y_pred_lgb_full,
122
+ 'KNN': y_pred_knn_full,
123
+ 'Combo': y_pred_combo_full,
124
+ 'Abs_Error': np.abs(y_full.values - y_pred_combo_full)
125
+ })
126
+
127
+ # Add back the full features for context
128
+ comparison_full = pd.concat([
129
+ X_full.reset_index(drop=True),
130
+ comparison_full.reset_index(drop=True)
131
+ ], axis=1)
132
+
133
+ # You can also add the original columns from df_clean for more context
134
+ comparison_full['Player'] = df_clean['Player'].values
135
+ comparison_full['Contest_Date'] = df_clean['Contest Date'].values
136
+ comparison_full['Pos'] = df_clean['Pos'].values
137
+
138
+ # Overall performance metrics on full dataset
139
+ print("\n=== Full Dataset Performance ===")
140
+ for model_name, predictions in [('XGB', y_pred_xgb_full), ('LGB', y_pred_lgb_full), ('KNN', y_pred_knn_full), ('Combo', y_pred_combo_full)]:
141
+ rmse = np.sqrt(mean_squared_error(y_full, predictions))
142
+ mae = mean_absolute_error(y_full, predictions)
143
+ r2 = r2_score(y_full, predictions)
144
+ print(f"{model_name:8} - RMSE: {rmse:6.2f}, MAE: {mae:6.2f}, R²: {r2:6.3f}")
145
+
146
+ # Analysis on full dataset
147
+ print("\n=== Highest Ownership (Full Data) ===")
148
+ print(comparison_full.sort_values('Actual_Exposure', ascending=False).head(10)[
149
+ ['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
150
+ ])
151
+
152
+ print("\n=== Highest Predicted Ownership (Full Data) ===")
153
+ print(comparison_full.sort_values('Combo', ascending=False).head(10)[
154
+ ['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
155
+ ])
156
+
157
+ print("\n=== Worst Predictions (Full Data) ===")
158
+ print(comparison_full.nlargest(10, 'Abs_Error')[
159
+ ['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
160
+ ])
161
+
162
+ print("\n=== Best Predictions (Full Data) ===")
163
+ print(comparison_full.nsmallest(10, 'Abs_Error')[
164
+ ['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
165
+ ])
166
+
167
+ # Prepare the current data with the same feature engineering
168
+ current_projections['Actual'] = current_projections['Median'] # Rename to match training
169
+ current_projections['value'] = current_projections['Actual'] / (current_projections['Salary'] / 1000)
170
+ current_projections['value_adv'] = current_projections['value'] - current_projections['value'].mean()
171
+ current_projections['actual_adv'] = current_projections['Actual'] - current_projections['Actual'].mean()
172
+
173
+ # Create the same engineered features
174
+ # Assuming all rows are from the same contest (current slate)
175
+ current_projections['contest_size'] = len(current_projections) # All players in current slate
176
+
177
+ # Create value_play feature (same logic as training)
178
+ current_projections['value_play'] = np.where(
179
+ (current_projections['Salary'] <= 4000) &
180
+ (current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 6.0),
181
+ 1, 0
182
+ )
183
+
184
+ current_projections['value_density'] = current_projections['value_play'].sum() / current_projections['Player'].count()
185
+
186
+ current_projections['base_ownership'] = 800.0 / current_projections['contest_size']
187
+
188
+ current_projections['strong_play'] = np.where((current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 6.0), 1, 0)
189
+ current_projections['punt_play'] = np.where((current_projections['Salary'] < 3500) & (current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 5.0), 1, 0)
190
+
191
+ current_projections['ownership_share'] = current_projections['Own'].sum() / current_projections['Player'].count() * 800
192
+ # Prepare features in the same order as training
193
+ X_current = current_projections[feature_cols]
194
+
195
+ # Make predictions with all your models
196
+ current_projections['XGB'] = np.clip(xgb_model.predict(X_current), 0, 100)
197
+ current_projections['LGB'] = np.clip(lgb_model.predict(X_current), 0, 100) * 100
198
+ current_projections['KNN'] = np.clip(knn_model.predict(X_current), 0, 100)
199
+
200
+ # Create combo prediction
201
+ current_projections['Combo'] = (
202
+ (current_projections['XGB'] * .30) +
203
+ (current_projections['LGB'] * .30) +
204
+ (current_projections['KNN'] * .40)
205
+ )
206
+
207
+ current_projections['Combo'] = np.where((current_projections['value'] < 5.0) & (current_projections['Salary'] < 9000), current_projections['Combo'] * .75, current_projections['Combo'])
208
+ current_projections['Combo'] = np.where((current_projections['Median'] < 18.0), current_projections['Combo'] * .33, current_projections['Combo'])
209
+ current_projections['Combo'] = np.where((current_projections['Salary'] > 5000) & (current_projections['value'] < 4.5), 1, current_projections['Combo'])
210
+ current_projections['Combo'] = np.where(current_projections['value'] > 6.0, current_projections['Combo'] * ((current_projections['value'] / 6.0)), current_projections['Combo'])
211
+ current_projections['Combo'] = np.where((current_projections['Salary'] > 9000), current_projections['Combo'] * .75, current_projections['Combo'])
212
+ current_projections['Combo'] = np.where((current_projections['Salary'] > 9000) & (current_projections['Combo'] < current_projections['value']), current_projections['value'], current_projections['Combo'])
213
+ current_projections['Combo'] = np.where((current_projections['Median'] > 20.0) & (current_projections['Salary'] < 3500), current_projections['Combo'] * (10 - current_projections['strong_play'].sum()).clip(0, 3), current_projections['Combo'])
214
+ current_projections['Combo'] = np.where(current_projections['Position'].str.contains('/'), current_projections['Combo'] * 1.25, current_projections['Combo'] * .75)
215
+
216
+ power_scale = 1.10
217
+ combo_powered = current_projections['Combo'] ** power_scale
218
+
219
+ norm_var = 800.0 / combo_powered.sum()
220
+ current_projections['Combo_powered'] = combo_powered * norm_var
221
+
222
+ # Display predictions sorted by predicted ownership
223
+ print("\n=== Current Slate - Predicted Ownership ===")
224
+ print(f'the strong_play count is {current_projections['strong_play'].sum()}')
225
+ display_cols = ['Player', 'Position', 'Salary', 'Median', 'value', 'Own', 'XGB', 'LGB', 'KNN', 'Combo', 'Combo_powered']
226
+ print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
227
+ print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
228
+ print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
229
+ print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
func/NHL_own_regress.py CHANGED
@@ -1,13 +1,74 @@
1
- """
2
- NHL Ownership Regression Models
3
- Pre-trained models for ownership prediction
4
- """
5
  import xgboost as xgb
6
  import lightgbm as lgb
 
 
 
 
7
  from sklearn.neighbors import KNeighborsRegressor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- # Create untrained model instances with default parameters
10
- # These will be used as-is or can be trained later if needed
11
  xgb_model = xgb.XGBRegressor(
12
  n_estimators=1000,
13
  learning_rate=0.10,
@@ -16,104 +77,28 @@ xgb_model = xgb.XGBRegressor(
16
  base_score=10
17
  )
18
 
 
 
19
  lgb_model = lgb.LGBMRegressor(
20
- n_estimators=1000,
21
- learning_rate=0.1,
22
- num_leaves=31,
23
  random_state=42,
24
- verbose=-1
25
  )
26
 
 
 
27
  knn_model = KNeighborsRegressor(
28
  n_neighbors=5,
29
- weights='distance'
30
  )
31
 
 
 
32
  __all__ = ['xgb_model', 'lgb_model', 'knn_model']
33
 
34
- # Training code moved to separate script to avoid slow imports
35
  if __name__ == '__main__':
36
- import pymongo
37
- import pandas as pd
38
- import numpy as np
39
- from sklearn.model_selection import train_test_split
40
- from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
41
-
42
- def init_conn():
43
- uri = "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
44
- client = pymongo.MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=500000)
45
- contest_db = client["Contest_Information"]
46
- nba_db = client["NHL_Database"]
47
- return contest_db, nba_db
48
-
49
- contest_db, nba_db = init_conn()
50
-
51
- collection = contest_db["NHL_reg_exposure_frames"]
52
- cursor = collection.find()
53
- raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
54
- raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
55
- raw_display = raw_display[raw_display['Actual'].between(1, 100)]
56
-
57
- print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
58
-
59
- collection = nba_db["Player_Level_ROO"]
60
- cursor = collection.find()
61
- raw_projections = pd.DataFrame(list(cursor))
62
- raw_projections = raw_projections[['Player', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '2x%', '3x%', '4x%', 'Own',
63
- 'Small Field Own%', 'Large Field Own%', 'Cash Own%', 'CPT_Own', 'Site', 'Type', 'Slate', 'player_id', 'timestamp']]
64
- raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
65
- raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
66
-
67
- current_projections = raw_projections[(raw_projections['Slate'] == 'Main Slate') & (raw_projections['Site'] == 'Draftkings')]
68
-
69
- intcols = ['Contest ID', 'Salary']
70
- floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
71
- percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
72
- stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
73
-
74
- for col in intcols:
75
- raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
76
- for col in floatcols:
77
- raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
78
- for col in percentagecols:
79
- raw_display[col] = raw_display[col] * 100.0
80
- for col in stringcols:
81
- raw_display[col] = raw_display[col].astype(str)
82
-
83
- df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
84
-
85
- df_clean['Actual'] = df_clean['Actual'] * .90
86
- df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
87
- df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
88
- df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
89
- df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
90
- df_clean['base_ownership'] = 900.0 / df_clean['contest_size']
91
- df_clean['value_play'] = np.where((df_clean['Salary'] <= 4500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
92
- df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
93
- df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
94
- df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
95
- df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
96
- lambda x: x / x.sum() * 900
97
- )
98
-
99
- # Prepare features and target
100
- feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
101
- X = df_clean[feature_cols]
102
- y = df_clean['ownership_share']
103
-
104
- # Train-test split
105
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
106
-
107
- # Train models
108
- print("Training XGBoost model...")
109
- xgb_model.fit(X_train, y_train)
110
-
111
- print("Training LightGBM model...")
112
- lgb_model.fit(X_train, y_train / 100)
113
-
114
- print("Training KNN model...")
115
- knn_model.fit(X_train, y_train)
116
-
117
  X_full = df_clean[feature_cols]
118
  y_full = df_clean['Exposure Overall']
119
 
@@ -232,4 +217,4 @@ if __name__ == '__main__':
232
  print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
233
  print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
234
  print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
235
- print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
 
1
+ import pymongo
2
+ import pandas as pd
3
+ import numpy as np
 
4
  import xgboost as xgb
5
  import lightgbm as lgb
6
+
7
+ from sklearn.model_selection import train_test_split
8
+ from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
9
+ from sklearn.svm import SVR
10
  from sklearn.neighbors import KNeighborsRegressor
11
+ from sklearn.linear_model import LinearRegression
12
+
13
+ from src.database import *
14
+
15
+ collection = contest_db["NHL_reg_exposure_frames"]
16
+ cursor = collection.find()
17
+ raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
18
+ raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
19
+ raw_display = raw_display[raw_display['Actual'].between(1, 100)]
20
+
21
+ print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
22
+
23
+ collection = nhl_db["Player_Level_ROO"]
24
+ cursor = collection.find()
25
+ raw_projections = pd.DataFrame(list(cursor))
26
+ raw_projections = raw_projections[['Player', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '2x%', '3x%', '4x%', 'Own',
27
+ 'Small Field Own%', 'Large Field Own%', 'Cash Own%', 'CPT_Own', 'Site', 'Type', 'Slate', 'player_id', 'timestamp']]
28
+ raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
29
+ raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
30
+
31
+ current_projections = raw_projections[(raw_projections['Slate'] == 'Main Slate') & (raw_projections['Site'] == 'Draftkings')]
32
+
33
+ intcols = ['Contest ID', 'Salary']
34
+ floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
35
+ percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
36
+ stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
37
+
38
+ for col in intcols:
39
+ raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
40
+ for col in floatcols:
41
+ raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
42
+ for col in percentagecols:
43
+ raw_display[col] = raw_display[col] * 100.0
44
+ for col in stringcols:
45
+ raw_display[col] = raw_display[col].astype(str)
46
+
47
+ df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
48
+
49
+ df_clean['Actual'] = df_clean['Actual'] * .90
50
+ df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
51
+ df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
52
+ df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
53
+ df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
54
+ df_clean['base_ownership'] = 900.0 / df_clean['contest_size']
55
+ df_clean['value_play'] = np.where((df_clean['Salary'] <= 4500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
56
+ df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
57
+ df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
58
+ df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
59
+ df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
60
+ lambda x: x / x.sum() * 900
61
+ )
62
+
63
+ # Prepare features and target
64
+ feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
65
+ X = df_clean[feature_cols]
66
+ y = df_clean['ownership_share']
67
+
68
+ # Train-test split
69
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
70
 
71
+ # Create and train model
 
72
  xgb_model = xgb.XGBRegressor(
73
  n_estimators=1000,
74
  learning_rate=0.10,
 
77
  base_score=10
78
  )
79
 
80
+ xgb_model.fit(X_train, y_train)
81
+
82
  lgb_model = lgb.LGBMRegressor(
83
+ n_estimators=1000, # number of boosting rounds
84
+ learning_rate=0.1, # learning rate
85
+ num_leaves=31, # max number of leaves in one tree
86
  random_state=42,
87
+ verbose=-1 # suppress warnings during training
88
  )
89
 
90
+ lgb_model.fit(X_train, y_train / 100)
91
+
92
  knn_model = KNeighborsRegressor(
93
  n_neighbors=5,
94
+ weights='distance' # or 'uniform'
95
  )
96
 
97
+ knn_model.fit(X_train, y_train)
98
+
99
  __all__ = ['xgb_model', 'lgb_model', 'knn_model']
100
 
 
101
  if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  X_full = df_clean[feature_cols]
103
  y_full = df_clean['Exposure Overall']
104
 
 
217
  print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
218
  print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
219
  print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
220
+ print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
func/dk_nba_go/NBA_seed_frames.go ADDED
@@ -0,0 +1,1133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ PG int32 `bson:"PG"`
83
+ SG int32 `bson:"SG"`
84
+ SF int32 `bson:"SF"`
85
+ PF int32 `bson:"PF"`
86
+ C int32 `bson:"C"`
87
+ G int32 `bson:"G"`
88
+ F int32 `bson:"F"`
89
+ FLEX int32 `bson:"FLEX"`
90
+ CreatedAt time.Time `bson:"created_at"`
91
+ }
92
+
93
+ func loadPlayerData() (*ProcessedData, error) {
94
+ data, err := ioutil.ReadFile("dk_nba_go/player_data.json")
95
+ if err != nil {
96
+ return nil, fmt.Errorf("failed to read in data: %v", err)
97
+ }
98
+
99
+ var processedData ProcessedData
100
+ if err := json.Unmarshal(data, &processedData); err != nil {
101
+ return nil, fmt.Errorf("failed to parse json: %v", err)
102
+ }
103
+
104
+ return &processedData, nil
105
+ }
106
+
107
+ func loadOptimals() (map[string]LineupData, error) {
108
+ data, err := ioutil.ReadFile("dk_nba_go/optimal_lineups.json")
109
+ if err != nil {
110
+ return nil, fmt.Errorf("failed to parse optimals: %v", err)
111
+ }
112
+
113
+ type OptimalsJSON struct {
114
+ Slate string `json:"slate"`
115
+ Salary int32 `json:"salary"`
116
+ Projection float64 `json:"projection"`
117
+ Team string `json:"team"`
118
+ Team_count int32 `json:"team_count"`
119
+ Secondary string `json:"secondary"`
120
+ Secondary_count int32 `json:"secondary_count"`
121
+ Ownership float64 `json:"ownership"`
122
+ Players []int32 `json:"players"`
123
+ }
124
+
125
+ var allOptimals []OptimalsJSON
126
+ if err := json.Unmarshal(data, &allOptimals); err != nil {
127
+ return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
128
+ }
129
+
130
+ optimalsBySlate := make(map[string]LineupData)
131
+
132
+ for _, optimal := range allOptimals {
133
+ if _, exists := optimalsBySlate[optimal.Slate]; !exists {
134
+ optimalsBySlate[optimal.Slate] = LineupData{
135
+ Salary: []int32{},
136
+ Projection: []float64{},
137
+ Team: []string{},
138
+ Team_count: []int32{},
139
+ Secondary: []string{},
140
+ Secondary_count: []int32{},
141
+ Ownership: []float64{},
142
+ Players: [][]int32{},
143
+ }
144
+ }
145
+
146
+ slateData := optimalsBySlate[optimal.Slate]
147
+ slateData.Salary = append(slateData.Salary, optimal.Salary)
148
+ slateData.Projection = append(slateData.Projection, optimal.Projection)
149
+ slateData.Team = append(slateData.Team, optimal.Team)
150
+ slateData.Team_count = append(slateData.Team_count, optimal.Team_count)
151
+ slateData.Secondary = append(slateData.Secondary, optimal.Secondary)
152
+ slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count)
153
+ slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
154
+ slateData.Players = append(slateData.Players, optimal.Players)
155
+
156
+ optimalsBySlate[optimal.Slate] = slateData
157
+ }
158
+
159
+ return optimalsBySlate, nil
160
+ }
161
+
162
+ func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
163
+ if len(optimals.Salary) == 0 {
164
+ return results
165
+ }
166
+
167
+ // Simply append the optimal LineupData to existing results
168
+ return append(results, optimals)
169
+ }
170
+
171
+ func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
172
+ salaryMap := make(map[int32]int32)
173
+ projMap := make(map[int32]float64)
174
+ ownMap := make(map[int32]float64)
175
+ teamMap := make(map[int32]string)
176
+
177
+ for keyStr, value := range playerSet.Maps.SalaryMap {
178
+ key, err := strconv.Atoi(keyStr)
179
+ if err != nil {
180
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
181
+ continue
182
+ }
183
+ salaryMap[int32(key)] = value
184
+ }
185
+
186
+ for keyStr, value := range playerSet.Maps.ProjectionMap {
187
+ key, err := strconv.Atoi(keyStr)
188
+ if err != nil {
189
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
190
+ continue
191
+ }
192
+ projMap[int32(key)] = value
193
+ }
194
+
195
+ for keyStr, value := range playerSet.Maps.OwnershipMap {
196
+ key, err := strconv.Atoi(keyStr)
197
+ if err != nil {
198
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
199
+ continue
200
+ }
201
+ ownMap[int32(key)] = value
202
+ }
203
+
204
+ for keyStr, value := range playerSet.Maps.TeamMap {
205
+ key, err := strconv.Atoi(keyStr)
206
+ if err != nil {
207
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
208
+ continue
209
+ }
210
+ teamMap[int32(key)] = value
211
+ }
212
+
213
+ return salaryMap, projMap, ownMap, teamMap
214
+ }
215
+
216
+ func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
217
+ result := make([]U, len(input))
218
+
219
+ for i, key := range input {
220
+ if value, exists := valueMap[key]; exists {
221
+ result[i] = value
222
+ } else {
223
+ var zero U
224
+ result[i] = zero
225
+ }
226
+ }
227
+
228
+ return result
229
+ }
230
+
231
+ func sortChars(strData string) string {
232
+ runes := []rune(strData)
233
+ slices.Sort(runes)
234
+ return string(runes)
235
+ }
236
+
237
+ func rowMostCommon(row []int) (*int, *int) {
238
+ if len(row) == 0 {
239
+ return nil, nil
240
+ }
241
+
242
+ counts := make(map[int]int)
243
+ for _, value := range row {
244
+ counts[value]++
245
+ }
246
+
247
+ if len(counts) < 2 {
248
+ return nil, nil
249
+ }
250
+
251
+ mostCommon := 0
252
+ maxCount := 0
253
+ secondMost := 0
254
+ secondMax := 0
255
+
256
+ for value, count := range counts {
257
+ if count > maxCount {
258
+ secondMax = maxCount
259
+ secondMost = mostCommon
260
+
261
+ maxCount = count
262
+ mostCommon = value
263
+ } else if count > secondMax && count < maxCount {
264
+ secondMax = count
265
+ secondMost = value
266
+ }
267
+
268
+ }
269
+
270
+ return &mostCommon, &secondMost
271
+ }
272
+
273
+ func rowBiggestAndSecond(row []int) (int, int) {
274
+ if len(row) == 0 {
275
+ return 0, 0
276
+ }
277
+
278
+ counts := make(map[int]int)
279
+ for _, value := range row {
280
+ counts[value]++
281
+ }
282
+
283
+ if len(counts) == 1 {
284
+ return len(row), 0
285
+ }
286
+
287
+ biggestVal := 0
288
+ secondBiggestVal := 0
289
+
290
+ for _, count := range counts {
291
+ if count > biggestVal {
292
+ secondBiggestVal = biggestVal
293
+ biggestVal = count
294
+ } else if count > secondBiggestVal && count < biggestVal {
295
+ secondBiggestVal = count
296
+ }
297
+ }
298
+
299
+ return biggestVal, secondBiggestVal
300
+ }
301
+
302
+ func createOverallDFs(players []Player, pos string) PlayerData {
303
+ var filteredPlayers []Player
304
+ for _, player := range players {
305
+ if pos == "FLEX" {
306
+ filteredPlayers = append(filteredPlayers, player)
307
+ } else {
308
+ if strings.Contains(player.Position, pos) {
309
+ filteredPlayers = append(filteredPlayers, player)
310
+ }
311
+ }
312
+ }
313
+
314
+ nameMap := make(map[int]string)
315
+ for i, player := range filteredPlayers {
316
+ nameMap[i] = player.Name
317
+ }
318
+
319
+ return PlayerData{
320
+ Players: filteredPlayers,
321
+ NameMap: nameMap,
322
+ }
323
+ }
324
+
325
+ func sumSalaryRows(data [][]int32) []int32 {
326
+ result := make([]int32, len(data))
327
+
328
+ for i, row := range data {
329
+ var sum int32
330
+ for _, value := range row {
331
+ sum += value
332
+ }
333
+ result[i] = sum
334
+ }
335
+
336
+ return result
337
+ }
338
+
339
+ func sumOwnRows(data [][]float64) []float64 {
340
+ result := make([]float64, len(data))
341
+
342
+ for i, row := range data {
343
+ var sum float64
344
+ for _, value := range row {
345
+ sum += value
346
+ }
347
+ result[i] = sum
348
+ }
349
+
350
+ return result
351
+ }
352
+
353
+ func sumProjRows(data [][]float64) []float64 {
354
+ result := make([]float64, len(data))
355
+
356
+ for i, row := range data {
357
+ var sum float64
358
+ for _, value := range row {
359
+ sum += value
360
+ }
361
+ result[i] = sum
362
+ }
363
+
364
+ return result
365
+ }
366
+
367
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
368
+ var validIndicies []int
369
+
370
+ for i, value := range values {
371
+ if value <= maxVal {
372
+ validIndicies = append(validIndicies, i)
373
+ }
374
+ }
375
+
376
+ return validIndicies
377
+ }
378
+
379
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
380
+ var validIndicies []int
381
+
382
+ for i, value := range values {
383
+ if value >= minVal {
384
+ validIndicies = append(validIndicies, i)
385
+ }
386
+ }
387
+
388
+ return validIndicies
389
+ }
390
+
391
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
392
+ result := make([]T, len(indicies))
393
+
394
+ for i, idx := range indicies {
395
+ result[i] = data[idx]
396
+ }
397
+
398
+ return result
399
+ }
400
+
401
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
402
+ indicies := make([]int, len(data.Ownership))
403
+ for i := range indicies {
404
+ indicies[i] = i
405
+ }
406
+
407
+ switch field {
408
+ case "salary":
409
+ sort.Slice(indicies, func(i, j int) bool {
410
+ if ascending {
411
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
412
+ }
413
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
414
+ })
415
+ case "projection":
416
+ sort.Slice(indicies, func(i, j int) bool {
417
+ if ascending {
418
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
419
+ }
420
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
421
+ })
422
+ case "ownership":
423
+ sort.Slice(indicies, func(i, j int) bool {
424
+ if ascending {
425
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
426
+ }
427
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
428
+ })
429
+ default:
430
+ sort.Slice(indicies, func(i, j int) bool {
431
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
432
+ })
433
+ }
434
+
435
+ return LineupData{
436
+ Salary: sliceByIndicies(data.Salary, indicies),
437
+ Projection: sliceByIndicies(data.Projection, indicies),
438
+ Team: sliceByIndicies(data.Team, indicies),
439
+ Team_count: sliceByIndicies(data.Team_count, indicies),
440
+ Secondary: sliceByIndicies(data.Secondary, indicies),
441
+ Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
442
+ Ownership: sliceByIndicies(data.Ownership, indicies),
443
+ Players: sliceByIndicies(data.Players, indicies),
444
+ }
445
+ }
446
+
447
+ func combineArrays(pg, sg, sf, pf, c, g, f, flex []int32) [][]int32 {
448
+ length := len(pg)
449
+
450
+ result := make([][]int32, length)
451
+
452
+ for i := 0; i < length; i++ {
453
+ result[i] = []int32{
454
+ pg[i],
455
+ sg[i],
456
+ sf[i],
457
+ pf[i],
458
+ c[i],
459
+ g[i],
460
+ f[i],
461
+ flex[i],
462
+ }
463
+ }
464
+
465
+ return result
466
+ }
467
+
468
+ func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
469
+
470
+ salaries := make([][]int32, len(combinedArrays))
471
+ projections := make([][]float64, len(combinedArrays))
472
+ ownership := make([][]float64, len(combinedArrays))
473
+ teamArrays := make([][]string, len(combinedArrays))
474
+
475
+ for i, row := range combinedArrays {
476
+
477
+ players := row[0:8]
478
+
479
+ playerSalaries := processAndFill(players, salaryMap)
480
+ playerProjections := processAndFill(players, projMap)
481
+ playerOwnership := processAndFill(players, ownMap)
482
+ playerTeams := processAndFill(players, teamMap)
483
+
484
+ salaries[i] = playerSalaries
485
+ projections[i] = playerProjections
486
+ ownership[i] = playerOwnership
487
+ teamArrays[i] = playerTeams
488
+ }
489
+
490
+ totalSalaries := sumSalaryRows(salaries)
491
+ totalProjections := sumProjRows(projections)
492
+ totalOwnership := sumOwnRows(ownership)
493
+
494
+ teamData := make([]string, len(teamArrays))
495
+ teamCounts := make([]int32, len(teamArrays))
496
+ secondaryData := make([]string, len(teamArrays))
497
+ secondaryCounts := make([]int32, len(teamArrays))
498
+
499
+ for i, teams := range teamArrays {
500
+ teamMap := make(map[string]int)
501
+ uniqueTeams := make([]string, 0)
502
+ for _, team := range teams {
503
+ if _, exists := teamMap[team]; !exists {
504
+ teamMap[team] = len(uniqueTeams)
505
+ uniqueTeams = append(uniqueTeams, team)
506
+ }
507
+ }
508
+
509
+ teamInts := make([]int, len(teams))
510
+ for j, team := range teams {
511
+ teamInts[j] = teamMap[team]
512
+ }
513
+
514
+ mostCommon, secondMost := rowMostCommon(teamInts)
515
+ if mostCommon != nil {
516
+ teamData[i] = uniqueTeams[*mostCommon]
517
+ teamCount, _ := rowBiggestAndSecond(teamInts)
518
+ teamCounts[i] = int32(teamCount)
519
+ }
520
+ if secondMost != nil {
521
+ secondaryData[i] = uniqueTeams[*secondMost]
522
+ _, secondaryCount := rowBiggestAndSecond(teamInts)
523
+ secondaryCounts[i] = int32(secondaryCount)
524
+ }
525
+ }
526
+
527
+ var validIndicies []int
528
+ if site == "DK" {
529
+ validIndicies = filterMax(totalSalaries, int32(50000))
530
+ } else {
531
+ validIndicies = filterMax(totalSalaries, int32(60000))
532
+ }
533
+
534
+ validData := LineupData{
535
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
536
+ Projection: sliceByIndicies(totalProjections, validIndicies),
537
+ Team: sliceByIndicies(teamData, validIndicies),
538
+ Team_count: sliceByIndicies(teamCounts, validIndicies),
539
+ Secondary: sliceByIndicies(secondaryData, validIndicies),
540
+ Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
541
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
542
+ Players: sliceByIndicies(combinedArrays, validIndicies),
543
+ }
544
+
545
+ return sortDataByField(validData, "projection", false)
546
+ }
547
+
548
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
549
+ if len(values) == 0 {
550
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
551
+ }
552
+
553
+ if quantile < 0 || quantile > 1 {
554
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
555
+ }
556
+
557
+ sorted := make([]float64, len(values))
558
+ copy(sorted, values)
559
+ sort.Float64s(sorted)
560
+
561
+ index := int(float64(len(sorted)-1) * quantile)
562
+ return sorted[index], nil
563
+ }
564
+
565
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
566
+ if count > len(playerIDs) {
567
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
568
+ }
569
+
570
+ shuffled := make([]int32, len(playerIDs))
571
+ copy(shuffled, playerIDs)
572
+
573
+ for i := len(shuffled) - 1; i > 0; i-- {
574
+ j := rng.Intn(i + 1)
575
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
576
+ }
577
+
578
+ return shuffled[:count], nil
579
+ }
580
+
581
+ func generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, gPlayers, fPlayers, flexPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
582
+
583
+ // DEBUG: Check pool sizes
584
+ fmt.Printf("DEBUG - Pool sizes: PG=%d, SG=%d, SF=%d, PF=%d, C=%d\n",
585
+ len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
586
+
587
+ if len(pgPlayers) == 0 || len(sgPlayers) == 0 || len(sfPlayers) == 0 || len(pfPlayers) == 0 || len(cPlayers) == 0 || len(gPlayers) == 0 || len(fPlayers) == 0 || len(flexPlayers) == 0 {
588
+ return nil, fmt.Errorf("one or more position pools is empty: PG=%d, SG=%d, SF=%d, PF=%d, C=%d, G=%d, F=%d, FLEX=%d",
589
+ len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers), len(gPlayers), len(fPlayers), len(flexPlayers))
590
+ }
591
+
592
+ var validArrays [][]int32
593
+ attempts := 0
594
+ maxAttempts := numRows * 10
595
+
596
+ for len(validArrays) < numRows && attempts < maxAttempts {
597
+ attempts++
598
+
599
+ pg := pgPlayers[rng.Intn(len(pgPlayers))]
600
+ sg := sgPlayers[rng.Intn(len(sgPlayers))]
601
+ sf := sfPlayers[rng.Intn(len(sfPlayers))]
602
+ pf := pfPlayers[rng.Intn(len(pfPlayers))]
603
+ c := cPlayers[rng.Intn(len(cPlayers))]
604
+ g := gPlayers[rng.Intn(len(gPlayers))]
605
+ f := fPlayers[rng.Intn(len(fPlayers))]
606
+ flex := flexPlayers[rng.Intn(len(flexPlayers))]
607
+
608
+ if pg.Name != sg.Name && pg.Name != sf.Name && pg.Name != pf.Name && pg.Name != g.Name && pg.Name != f.Name && pg.Name != flex.Name && sg.Name != sf.Name &&
609
+ sg.Name != pf.Name && sg.Name != g.Name && sg.Name != f.Name && sg.Name != flex.Name && sf.Name != pf.Name && sf.Name != g.Name && sf.Name != f.Name &&
610
+ sf.Name != flex.Name && pf.Name != c.Name && pf.Name != g.Name && pf.Name != f.Name && pf.Name != flex.Name && c.Name != f.Name && c.Name != flex.Name &&
611
+ g.Name != f.Name && g.Name != flex.Name && f.Name != flex.Name {
612
+
613
+ playerIDs := []int32{pg.ID, sg.ID, sf.ID, pf.ID, c.ID, g.ID, f.ID, flex.ID}
614
+ validArrays = append(validArrays, playerIDs)
615
+ }
616
+ }
617
+
618
+ if len(validArrays) == 0 {
619
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
620
+ }
621
+
622
+ return validArrays, nil
623
+ }
624
+
625
+ func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
626
+ if len(players) == 0 {
627
+ return nil, fmt.Errorf("no players provided")
628
+ }
629
+
630
+ var filteredPlayers []Player
631
+ for _, player := range players {
632
+ if pos == "FLEX" {
633
+ filteredPlayers = append(filteredPlayers, player)
634
+ } else {
635
+ if strings.Contains(player.Position, pos) {
636
+ filteredPlayers = append(filteredPlayers, player)
637
+ }
638
+ }
639
+ }
640
+
641
+ ownVals := make([]float64, len(filteredPlayers))
642
+ for i, player := range filteredPlayers {
643
+ ownVals[i] = player.OwnValue
644
+ }
645
+
646
+ threshold, err := calculateQuantile(ownVals, strengthStep)
647
+ if err != nil {
648
+ return nil, err
649
+ }
650
+
651
+ var filtered []Player
652
+ for _, player := range filteredPlayers {
653
+ if player.OwnValue >= threshold {
654
+ filtered = append(filtered, player)
655
+ }
656
+ }
657
+
658
+ if len(filtered) == 0 {
659
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
660
+ }
661
+
662
+ return filtered, nil
663
+ }
664
+
665
+ 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) {
666
+
667
+ pgPlayers, err := filterPosPlayersInQuantile(players, "PG", strengthStep)
668
+ if err != nil {
669
+ return LineupData{}, fmt.Errorf("failed to filter PG players: %v", err)
670
+ }
671
+
672
+ sgPlayers, err := filterPosPlayersInQuantile(players, "SG", strengthStep)
673
+ if err != nil {
674
+ return LineupData{}, fmt.Errorf("failed to filter SG players: %v", err)
675
+ }
676
+
677
+ sfPlayers, err := filterPosPlayersInQuantile(players, "SF", strengthStep)
678
+ if err != nil {
679
+ return LineupData{}, fmt.Errorf("failed to filter SF players: %v", err)
680
+ }
681
+
682
+ pfPlayers, err := filterPosPlayersInQuantile(players, "PF", strengthStep)
683
+ if err != nil {
684
+ return LineupData{}, fmt.Errorf("failed to filter PF players: %v", err)
685
+ }
686
+
687
+ cPlayers, err := filterPosPlayersInQuantile(players, "C", strengthStep)
688
+ if err != nil {
689
+ return LineupData{}, fmt.Errorf("failed to filter C players: %v", err)
690
+ }
691
+
692
+ gPlayers, err := filterPosPlayersInQuantile(players, "G", strengthStep)
693
+ if err != nil {
694
+ return LineupData{}, fmt.Errorf("failed to filter G players: %v", err)
695
+ }
696
+
697
+ fPlayers, err := filterPosPlayersInQuantile(players, "F", strengthStep)
698
+ if err != nil {
699
+ return LineupData{}, fmt.Errorf("failed to filter F players: %v", err)
700
+ }
701
+
702
+ flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep)
703
+ if err != nil {
704
+ return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err)
705
+ }
706
+
707
+ overallArrays, err := generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, gPlayers, fPlayers, flexPlayers, numRows, strengthStep, rng)
708
+ if err != nil {
709
+ return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
710
+ }
711
+
712
+ result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
713
+
714
+ return result, nil
715
+ }
716
+
717
+ 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) {
718
+ resultsChan := make(chan StrengthResult, len(strengthVars))
719
+
720
+ for i, strengthStep := range strengthVars {
721
+ go func(step float64, rows int, index int) {
722
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
723
+
724
+ result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
725
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
726
+
727
+ if err != nil {
728
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
729
+ } else {
730
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
731
+ }
732
+ }(strengthStep, rowsPerLevel[i], i)
733
+ }
734
+
735
+ allResults := make([]LineupData, len(strengthVars))
736
+ var errors []error
737
+ successCount := 0
738
+
739
+ for i := 0; i < len(strengthVars); i++ {
740
+ result := <-resultsChan
741
+ if result.Error != nil {
742
+ errors = append(errors, result.Error)
743
+ } else {
744
+ allResults[result.Index] = result.Data
745
+ successCount++
746
+ }
747
+ }
748
+
749
+ if successCount == 0 {
750
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
751
+ }
752
+
753
+ var validResults []LineupData
754
+ for i, result := range allResults {
755
+ if len(result.Salary) > 0 {
756
+ validResults = append(validResults, result)
757
+ } else {
758
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
759
+ }
760
+ }
761
+
762
+ fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
763
+ return validResults, nil
764
+ }
765
+
766
+ func printResults(results []LineupData, nameMap map[int32]string) {
767
+ fmt.Printf("Generated %d strength levels:\n", len(results))
768
+
769
+ // Combine all results into one big dataset
770
+ var allSalaries []int32
771
+ var allProjections []float64
772
+ var allOwnership []float64
773
+ var allPlayers [][]int32
774
+
775
+ for _, result := range results {
776
+ allSalaries = append(allSalaries, result.Salary...)
777
+ allProjections = append(allProjections, result.Projection...)
778
+ allOwnership = append(allOwnership, result.Ownership...)
779
+ allPlayers = append(allPlayers, result.Players...)
780
+ }
781
+
782
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
783
+
784
+ if len(allSalaries) > 0 {
785
+ // Print top 5 lineups (highest projection)
786
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
787
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
788
+ playerNames := []string{
789
+ getPlayerName(allPlayers[i][0], nameMap, "PG"),
790
+ getPlayerName(allPlayers[i][1], nameMap, "SG"),
791
+ getPlayerName(allPlayers[i][2], nameMap, "SF"),
792
+ getPlayerName(allPlayers[i][3], nameMap, "PF"),
793
+ getPlayerName(allPlayers[i][4], nameMap, "C"),
794
+ getPlayerName(allPlayers[i][5], nameMap, "G"),
795
+ getPlayerName(allPlayers[i][6], nameMap, "F"),
796
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
797
+ }
798
+
799
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
800
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
801
+ fmt.Printf(" Players: PG=%s, SG=%s, SF=%s, PF=%s, C=%s, G=%s, F=%s, FLEX=%s\n",
802
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
803
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7])
804
+ }
805
+
806
+ // Print bottom 5 lineups (lowest projection)
807
+ if len(allSalaries) > 5 {
808
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
809
+ start := len(allSalaries) - 5
810
+ for i := start; i < len(allSalaries); i++ {
811
+ // Convert player IDs to names
812
+ playerNames := []string{
813
+ getPlayerName(allPlayers[i][0], nameMap, "PG"),
814
+ getPlayerName(allPlayers[i][1], nameMap, "SG"),
815
+ getPlayerName(allPlayers[i][2], nameMap, "SF"),
816
+ getPlayerName(allPlayers[i][3], nameMap, "PF"),
817
+ getPlayerName(allPlayers[i][4], nameMap, "C"),
818
+ getPlayerName(allPlayers[i][5], nameMap, "G"),
819
+ getPlayerName(allPlayers[i][6], nameMap, "F"),
820
+ getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
821
+ }
822
+
823
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
824
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
825
+ fmt.Printf(" Players: PG=%s, SG=%s, SF=%s, PF=%s, C=%s, G=%s, F=%s, FLEX=%s\n",
826
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
827
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7])
828
+ }
829
+ }
830
+ }
831
+ }
832
+
833
+ func removeDuplicates(results []LineupData) []LineupData {
834
+ seen := make(map[string]bool)
835
+ var uniqueLineups []LineupData
836
+
837
+ for _, result := range results {
838
+ for i := 0; i < len(result.Players); i++ {
839
+ // Create combo string like Python
840
+ combo := fmt.Sprintf("%d%d%d%d%d%d%d%d",
841
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
842
+ result.Players[i][3], result.Players[i][4], result.Players[i][5],
843
+ result.Players[i][6], result.Players[i][7])
844
+
845
+ // Sort combo like Python
846
+ sortedCombo := sortChars(combo)
847
+
848
+ if !seen[sortedCombo] {
849
+ seen[sortedCombo] = true
850
+ uniqueLineups = append(uniqueLineups, LineupData{
851
+ Salary: []int32{result.Salary[i]},
852
+ Projection: []float64{result.Projection[i]},
853
+ Team: []string{result.Team[i]},
854
+ Team_count: []int32{result.Team_count[i]},
855
+ Secondary: []string{result.Secondary[i]},
856
+ Secondary_count: []int32{result.Secondary_count[i]},
857
+ Ownership: []float64{result.Ownership[i]},
858
+ Players: [][]int32{result.Players[i]},
859
+ })
860
+ }
861
+ }
862
+ }
863
+
864
+ if len(uniqueLineups) == 0 {
865
+ return []LineupData{}
866
+ }
867
+
868
+ var allSalary []int32
869
+ var allProjection []float64
870
+ var allTeam []string
871
+ var allTeamCount []int32
872
+ var allSecondary []string
873
+ var allSecondaryCount []int32
874
+ var allOwnership []float64
875
+ var allPlayers [][]int32
876
+
877
+ for _, lineup := range uniqueLineups {
878
+ allSalary = append(allSalary, lineup.Salary[0])
879
+ allProjection = append(allProjection, lineup.Projection[0])
880
+ allTeam = append(allTeam, lineup.Team[0])
881
+ allTeamCount = append(allTeamCount, lineup.Team_count[0])
882
+ allSecondary = append(allSecondary, lineup.Secondary[0])
883
+ allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
884
+ allOwnership = append(allOwnership, lineup.Ownership[0])
885
+ allPlayers = append(allPlayers, lineup.Players[0])
886
+ }
887
+
888
+ return []LineupData{{
889
+ Salary: allSalary,
890
+ Projection: allProjection,
891
+ Team: allTeam,
892
+ Team_count: allTeamCount,
893
+ Secondary: allSecondary,
894
+ Secondary_count: allSecondaryCount,
895
+ Ownership: allOwnership,
896
+ Players: allPlayers,
897
+ }}
898
+ }
899
+
900
+ func connectToMongoDB() (*mongo.Client, error) {
901
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
902
+
903
+ clientOptions := options.Client().
904
+ ApplyURI(uri).
905
+ SetRetryWrites(true).
906
+ SetServerSelectionTimeout(10 * time.Second).
907
+ SetMaxPoolSize(100).
908
+ SetMinPoolSize(10).
909
+ SetMaxConnIdleTime(30 * time.Second).
910
+ SetRetryReads(true)
911
+
912
+ client, err := mongo.Connect(context.TODO(), clientOptions)
913
+ if err != nil {
914
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
915
+ }
916
+
917
+ err = client.Ping(context.TODO(), nil)
918
+ if err != nil {
919
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
920
+ }
921
+
922
+ fmt.Printf("Connected to MongoDB!")
923
+ return client, nil
924
+ }
925
+
926
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
927
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
928
+
929
+ collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
930
+ collection := db.Collection(collectionName)
931
+
932
+ err := collection.Drop(context.TODO())
933
+ if err != nil {
934
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
935
+ }
936
+
937
+ var documents []interface{}
938
+
939
+ for _, result := range results {
940
+
941
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
942
+ fmt.Printf("Warning: Empty result found, skipping\n")
943
+ continue
944
+ }
945
+
946
+ for i := 0; i < len(result.Salary); i++ {
947
+ if len(result.Players[i]) < 8 {
948
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 8\n", i, len(result.Players[i]))
949
+ }
950
+
951
+ doc := LineupDocument{
952
+ Salary: result.Salary[i],
953
+ Projection: result.Projection[i],
954
+ Team: result.Team[i],
955
+ Team_count: result.Team_count[i],
956
+ Secondary: result.Secondary[i],
957
+ Secondary_count: result.Secondary_count[i],
958
+ Ownership: result.Ownership[i],
959
+ PG: result.Players[i][0],
960
+ SG: result.Players[i][1],
961
+ SF: result.Players[i][2],
962
+ PF: result.Players[i][3],
963
+ C: result.Players[i][4],
964
+ G: result.Players[i][5],
965
+ F: result.Players[i][6],
966
+ FLEX: result.Players[i][7],
967
+ CreatedAt: time.Now(),
968
+ }
969
+
970
+ documents = append(documents, doc)
971
+ }
972
+ }
973
+
974
+ if len(documents) == 0 {
975
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
976
+ }
977
+
978
+ if len(documents) > 500000 {
979
+ documents = documents[:500000]
980
+ }
981
+
982
+ chunkSize := 250000
983
+ for i := 0; i < len(documents); i += chunkSize {
984
+ end := i + chunkSize
985
+ if end > len(documents) {
986
+ end = len(documents)
987
+ }
988
+
989
+ chunk := documents[i:end]
990
+
991
+ for attempt := 0; attempt < 5; attempt++ {
992
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
993
+
994
+ opts := options.InsertMany().SetOrdered(false)
995
+ _, err := collection.InsertMany(ctx, chunk, opts)
996
+ cancel()
997
+
998
+ if err == nil {
999
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
1000
+ break
1001
+ }
1002
+
1003
+ fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
1004
+ if attempt < 4 {
1005
+ time.Sleep(1 * time.Second)
1006
+ }
1007
+ }
1008
+
1009
+ if err != nil {
1010
+ return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
1011
+ }
1012
+ }
1013
+
1014
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
1015
+ return nil
1016
+ }
1017
+
1018
+ func groupPlayersBySlate(players []Player) map[string][]Player {
1019
+ slateGroups := make(map[string][]Player)
1020
+
1021
+ for _, player := range players {
1022
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
1023
+ }
1024
+
1025
+ return slateGroups
1026
+ }
1027
+
1028
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
1029
+ if name, exists := nameMap[playerID]; exists && name != "" {
1030
+ return name
1031
+ }
1032
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
1033
+ }
1034
+
1035
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
1036
+ nameMap := make(map[int32]string)
1037
+
1038
+ for keyStr, value := range playerSet.Maps.NameMap {
1039
+ key, err := strconv.Atoi(keyStr)
1040
+ if err != nil {
1041
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
1042
+ continue
1043
+ }
1044
+ nameMap[int32(key)] = value
1045
+ }
1046
+
1047
+ return nameMap
1048
+ }
1049
+
1050
+ func main() {
1051
+ site := "DK"
1052
+ sport := "NBA"
1053
+ if len(os.Args) > 1 {
1054
+ site = os.Args[1]
1055
+ }
1056
+ if len(os.Args) > 2 {
1057
+ sport = os.Args[2]
1058
+ }
1059
+ processedData, err := loadPlayerData()
1060
+ if err != nil {
1061
+ fmt.Printf("Error loading data: %v\n", err)
1062
+ return
1063
+ }
1064
+
1065
+ start := time.Now()
1066
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
1067
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
1068
+
1069
+ SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
1070
+
1071
+ salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
1072
+
1073
+ nameMap := convertNamesToMaps(&processedData.PlayersMedian)
1074
+
1075
+ mongoClient, err := connectToMongoDB()
1076
+ if err != nil {
1077
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
1078
+ return
1079
+ }
1080
+ defer func() {
1081
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
1082
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
1083
+ }
1084
+ }()
1085
+
1086
+ optimalsBySlate, err := loadOptimals()
1087
+ if err != nil {
1088
+ fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
1089
+ optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
1090
+ } else {
1091
+ totalOptimals := 0
1092
+ for _, optimals := range optimalsBySlate {
1093
+ totalOptimals += len(optimals.Salary)
1094
+ }
1095
+ fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
1096
+ }
1097
+
1098
+ for slate, players := range SlateGroups {
1099
+
1100
+ fmt.Printf("Processing slate: %s\n", slate)
1101
+
1102
+ results, err := runSeedframeRoutines(
1103
+ players, strengthVars, rowsPerLevel,
1104
+ salaryMapJSON, projectionMapJSON,
1105
+ ownershipMapJSON, teamMapJSON, site)
1106
+
1107
+ if err != nil {
1108
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
1109
+ continue
1110
+ }
1111
+
1112
+ // Get optimal lineups for this specific slate
1113
+ slateOptimals := optimalsBySlate[slate]
1114
+
1115
+ // Append optimal lineups for this slate
1116
+ finalResults := appendOptimalLineups(results, slateOptimals)
1117
+
1118
+ exportResults := removeDuplicates(finalResults)
1119
+
1120
+ exportResults[0] = sortDataByField(exportResults[0], "projection", false)
1121
+
1122
+ err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
1123
+ if err != nil {
1124
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
1125
+ continue
1126
+ }
1127
+
1128
+ printResults(exportResults, nameMap)
1129
+ }
1130
+
1131
+ // Add this line at the end
1132
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
1133
+ }
func/fd_nba_go/NBA_seed_frames.go ADDED
@@ -0,0 +1,1118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ PG1 int32 `bson:"PG1"`
83
+ PG2 int32 `bson:"PG2"`
84
+ SG1 int32 `bson:"SG1"`
85
+ SG2 int32 `bson:"SG2"`
86
+ SF1 int32 `bson:"SF1"`
87
+ SF2 int32 `bson:"SF2"`
88
+ PF1 int32 `bson:"PF1"`
89
+ PF2 int32 `bson:"PF2"`
90
+ C int32 `bson:"C"`
91
+ CreatedAt time.Time `bson:"created_at"`
92
+ }
93
+
94
+ func loadPlayerData() (*ProcessedData, error) {
95
+ data, err := ioutil.ReadFile("fd_nba_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_nba_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 strings.Contains(player.Position, pos) {
307
+ filteredPlayers = append(filteredPlayers, player)
308
+ }
309
+ }
310
+
311
+ nameMap := make(map[int]string)
312
+ for i, player := range filteredPlayers {
313
+ nameMap[i] = player.Name
314
+ }
315
+
316
+ return PlayerData{
317
+ Players: filteredPlayers,
318
+ NameMap: nameMap,
319
+ }
320
+ }
321
+
322
+ func sumSalaryRows(data [][]int32) []int32 {
323
+ result := make([]int32, len(data))
324
+
325
+ for i, row := range data {
326
+ var sum int32
327
+ for _, value := range row {
328
+ sum += value
329
+ }
330
+ result[i] = sum
331
+ }
332
+
333
+ return result
334
+ }
335
+
336
+ func sumOwnRows(data [][]float64) []float64 {
337
+ result := make([]float64, len(data))
338
+
339
+ for i, row := range data {
340
+ var sum float64
341
+ for _, value := range row {
342
+ sum += value
343
+ }
344
+ result[i] = sum
345
+ }
346
+
347
+ return result
348
+ }
349
+
350
+ func sumProjRows(data [][]float64) []float64 {
351
+ result := make([]float64, len(data))
352
+
353
+ for i, row := range data {
354
+ var sum float64
355
+ for _, value := range row {
356
+ sum += value
357
+ }
358
+ result[i] = sum
359
+ }
360
+
361
+ return result
362
+ }
363
+
364
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
365
+ var validIndicies []int
366
+
367
+ for i, value := range values {
368
+ if value <= maxVal {
369
+ validIndicies = append(validIndicies, i)
370
+ }
371
+ }
372
+
373
+ return validIndicies
374
+ }
375
+
376
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
377
+ var validIndicies []int
378
+
379
+ for i, value := range values {
380
+ if value >= minVal {
381
+ validIndicies = append(validIndicies, i)
382
+ }
383
+ }
384
+
385
+ return validIndicies
386
+ }
387
+
388
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
389
+ result := make([]T, len(indicies))
390
+
391
+ for i, idx := range indicies {
392
+ result[i] = data[idx]
393
+ }
394
+
395
+ return result
396
+ }
397
+
398
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
399
+ indicies := make([]int, len(data.Ownership))
400
+ for i := range indicies {
401
+ indicies[i] = i
402
+ }
403
+
404
+ switch field {
405
+ case "salary":
406
+ sort.Slice(indicies, func(i, j int) bool {
407
+ if ascending {
408
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
409
+ }
410
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
411
+ })
412
+ case "projection":
413
+ sort.Slice(indicies, func(i, j int) bool {
414
+ if ascending {
415
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
416
+ }
417
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
418
+ })
419
+ case "ownership":
420
+ sort.Slice(indicies, func(i, j int) bool {
421
+ if ascending {
422
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
423
+ }
424
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
425
+ })
426
+ default:
427
+ sort.Slice(indicies, func(i, j int) bool {
428
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
429
+ })
430
+ }
431
+
432
+ return LineupData{
433
+ Salary: sliceByIndicies(data.Salary, indicies),
434
+ Projection: sliceByIndicies(data.Projection, indicies),
435
+ Team: sliceByIndicies(data.Team, indicies),
436
+ Team_count: sliceByIndicies(data.Team_count, indicies),
437
+ Secondary: sliceByIndicies(data.Secondary, indicies),
438
+ Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
439
+ Ownership: sliceByIndicies(data.Ownership, indicies),
440
+ Players: sliceByIndicies(data.Players, indicies),
441
+ }
442
+ }
443
+
444
+ func combineArrays(pg1, pg2, sg1, sg2, sf1, sf2, pf1, pf2, c []int32) [][]int32 {
445
+ length := len(pg1)
446
+
447
+ result := make([][]int32, length)
448
+
449
+ for i := 0; i < length; i++ {
450
+ result[i] = []int32{
451
+ pg1[i],
452
+ pg2[i],
453
+ sg1[i],
454
+ sg2[i],
455
+ sf1[i],
456
+ sf2[i],
457
+ pf1[i],
458
+ pf2[i],
459
+ c[i],
460
+ }
461
+ }
462
+
463
+ return result
464
+ }
465
+
466
+ func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
467
+
468
+ salaries := make([][]int32, len(combinedArrays))
469
+ projections := make([][]float64, len(combinedArrays))
470
+ ownership := make([][]float64, len(combinedArrays))
471
+ teamArrays := make([][]string, len(combinedArrays))
472
+
473
+ for i, row := range combinedArrays {
474
+
475
+ players := row[0:9]
476
+
477
+ playerSalaries := processAndFill(players, salaryMap)
478
+ playerProjections := processAndFill(players, projMap)
479
+ playerOwnership := processAndFill(players, ownMap)
480
+ playerTeams := processAndFill(players, teamMap)
481
+
482
+ salaries[i] = playerSalaries
483
+ projections[i] = playerProjections
484
+ ownership[i] = playerOwnership
485
+ teamArrays[i] = playerTeams
486
+ }
487
+
488
+ totalSalaries := sumSalaryRows(salaries)
489
+ totalProjections := sumProjRows(projections)
490
+ totalOwnership := sumOwnRows(ownership)
491
+
492
+ teamData := make([]string, len(teamArrays))
493
+ teamCounts := make([]int32, len(teamArrays))
494
+ secondaryData := make([]string, len(teamArrays))
495
+ secondaryCounts := make([]int32, len(teamArrays))
496
+
497
+ for i, teams := range teamArrays {
498
+ teamMap := make(map[string]int)
499
+ uniqueTeams := make([]string, 0)
500
+ for _, team := range teams {
501
+ if _, exists := teamMap[team]; !exists {
502
+ teamMap[team] = len(uniqueTeams)
503
+ uniqueTeams = append(uniqueTeams, team)
504
+ }
505
+ }
506
+
507
+ teamInts := make([]int, len(teams))
508
+ for j, team := range teams {
509
+ teamInts[j] = teamMap[team]
510
+ }
511
+
512
+ mostCommon, secondMost := rowMostCommon(teamInts)
513
+ if mostCommon != nil {
514
+ teamData[i] = uniqueTeams[*mostCommon]
515
+ teamCount, _ := rowBiggestAndSecond(teamInts)
516
+ teamCounts[i] = int32(teamCount)
517
+ }
518
+ if secondMost != nil {
519
+ secondaryData[i] = uniqueTeams[*secondMost]
520
+ _, secondaryCount := rowBiggestAndSecond(teamInts)
521
+ secondaryCounts[i] = int32(secondaryCount)
522
+ }
523
+ }
524
+
525
+ var validIndicies []int
526
+ if site == "DK" {
527
+ validIndicies = filterMax(totalSalaries, int32(50000))
528
+ } else {
529
+ validIndicies = filterMax(totalSalaries, int32(60000))
530
+ }
531
+
532
+ validData := LineupData{
533
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
534
+ Projection: sliceByIndicies(totalProjections, validIndicies),
535
+ Team: sliceByIndicies(teamData, validIndicies),
536
+ Team_count: sliceByIndicies(teamCounts, validIndicies),
537
+ Secondary: sliceByIndicies(secondaryData, validIndicies),
538
+ Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
539
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
540
+ Players: sliceByIndicies(combinedArrays, validIndicies),
541
+ }
542
+
543
+ return sortDataByField(validData, "projection", false)
544
+ }
545
+
546
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
547
+ if len(values) == 0 {
548
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
549
+ }
550
+
551
+ if quantile < 0 || quantile > 1 {
552
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
553
+ }
554
+
555
+ sorted := make([]float64, len(values))
556
+ copy(sorted, values)
557
+ sort.Float64s(sorted)
558
+
559
+ index := int(float64(len(sorted)-1) * quantile)
560
+ return sorted[index], nil
561
+ }
562
+
563
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
564
+ if count > len(playerIDs) {
565
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
566
+ }
567
+
568
+ shuffled := make([]int32, len(playerIDs))
569
+ copy(shuffled, playerIDs)
570
+
571
+ for i := len(shuffled) - 1; i > 0; i-- {
572
+ j := rng.Intn(i + 1)
573
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
574
+ }
575
+
576
+ return shuffled[:count], nil
577
+ }
578
+
579
+ func generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
580
+
581
+ // DEBUG: Check pool sizes
582
+ fmt.Printf("DEBUG - Pool sizes: PG=%d, SG=%d, SF=%d, PF=%d, C=%d\n",
583
+ len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
584
+
585
+ if len(pgPlayers) == 0 || len(sgPlayers) == 0 || len(sfPlayers) == 0 || len(pfPlayers) == 0 || len(cPlayers) == 0 {
586
+ return nil, fmt.Errorf("one or more position pools is empty: PG=%d, SG=%d, SF=%d, PF=%d, C=%d",
587
+ len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
588
+ }
589
+
590
+ var validArrays [][]int32
591
+ attempts := 0
592
+ maxAttempts := numRows * 10
593
+
594
+ for len(validArrays) < numRows && attempts < maxAttempts {
595
+ attempts++
596
+
597
+ pg1 := pgPlayers[rng.Intn(len(pgPlayers))]
598
+ pg2 := pgPlayers[rng.Intn(len(pgPlayers))]
599
+ sg1 := sgPlayers[rng.Intn(len(sgPlayers))]
600
+ sg2 := sgPlayers[rng.Intn(len(sgPlayers))]
601
+ sf1 := sfPlayers[rng.Intn(len(sfPlayers))]
602
+ sf2 := sfPlayers[rng.Intn(len(sfPlayers))]
603
+ pf1 := pfPlayers[rng.Intn(len(pfPlayers))]
604
+ pf2 := pfPlayers[rng.Intn(len(pfPlayers))]
605
+ c := cPlayers[rng.Intn(len(cPlayers))]
606
+
607
+ if pg1.Name != pg2.Name && pg1.Name != sg1.Name && pg1.Name != sg2.Name && pg1.Name != sf1.Name && pg1.Name != sf2.Name && pg1.Name != pf1.Name && pg1.Name != pf2.Name &&
608
+ pg2.Name != sg1.Name && pg2.Name != sg2.Name && pg2.Name != sf1.Name && pg2.Name != sf2.Name && pg2.Name != pf1.Name && pg2.Name != pf2.Name &&
609
+ sg1.Name != sg2.Name && sg1.Name != sf1.Name && sg1.Name != sf2.Name && sg1.Name != pf1.Name && sg1.Name != pf2.Name &&
610
+ sg2.Name != sf1.Name && sg2.Name != sf2.Name && sg2.Name != pf1.Name && sg2.Name != pf2.Name &&
611
+ sf1.Name != sf2.Name && sf1.Name != pf1.Name && sf1.Name != pf2.Name && sf2.Name != pf1.Name &&
612
+ sf2.Name != pf2.Name && pf1.Name != pf2.Name && pf1.Name != c.Name && pf2.Name != c.Name {
613
+
614
+ playerIDs := []int32{pg1.ID, pg2.ID, sg1.ID, sg2.ID, sf1.ID, sf2.ID, pf1.ID, pf2.ID, c.ID}
615
+ validArrays = append(validArrays, playerIDs)
616
+ }
617
+ }
618
+
619
+ if len(validArrays) == 0 {
620
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
621
+ }
622
+
623
+ return validArrays, nil
624
+ }
625
+
626
+ func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
627
+ if len(players) == 0 {
628
+ return nil, fmt.Errorf("no players provided")
629
+ }
630
+
631
+ var filteredPlayers []Player
632
+ for _, player := range players {
633
+ if strings.Contains(player.Position, pos) {
634
+ filteredPlayers = append(filteredPlayers, player)
635
+ }
636
+ }
637
+
638
+ ownVals := make([]float64, len(filteredPlayers))
639
+ for i, player := range filteredPlayers {
640
+ ownVals[i] = player.OwnValue
641
+ }
642
+
643
+ threshold, err := calculateQuantile(ownVals, strengthStep)
644
+ if err != nil {
645
+ return nil, err
646
+ }
647
+
648
+ var filtered []Player
649
+ for _, player := range filteredPlayers {
650
+ if player.OwnValue >= threshold {
651
+ filtered = append(filtered, player)
652
+ }
653
+ }
654
+
655
+ if len(filtered) == 0 {
656
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
657
+ }
658
+
659
+ return filtered, nil
660
+ }
661
+
662
+ 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) {
663
+
664
+ pgPlayers, err := filterPosPlayersInQuantile(players, "PG", strengthStep)
665
+ if err != nil {
666
+ return LineupData{}, fmt.Errorf("failed to filter PG players: %v", err)
667
+ }
668
+
669
+ sgPlayers, err := filterPosPlayersInQuantile(players, "SG", strengthStep)
670
+ if err != nil {
671
+ return LineupData{}, fmt.Errorf("failed to filter SG players: %v", err)
672
+ }
673
+
674
+ sfPlayers, err := filterPosPlayersInQuantile(players, "SF", strengthStep)
675
+ if err != nil {
676
+ return LineupData{}, fmt.Errorf("failed to filter SF players: %v", err)
677
+ }
678
+
679
+ pfPlayers, err := filterPosPlayersInQuantile(players, "PF", strengthStep)
680
+ if err != nil {
681
+ return LineupData{}, fmt.Errorf("failed to filter PF players: %v", err)
682
+ }
683
+
684
+ cPlayers, err := filterPosPlayersInQuantile(players, "C", strengthStep)
685
+ if err != nil {
686
+ return LineupData{}, fmt.Errorf("failed to filter C players: %v", err)
687
+ }
688
+
689
+ overallArrays, err := generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, numRows, strengthStep, rng)
690
+ if err != nil {
691
+ return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
692
+ }
693
+
694
+ result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
695
+
696
+ return result, nil
697
+ }
698
+
699
+ 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) {
700
+ resultsChan := make(chan StrengthResult, len(strengthVars))
701
+
702
+ for i, strengthStep := range strengthVars {
703
+ go func(step float64, rows int, index int) {
704
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
705
+
706
+ result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
707
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
708
+
709
+ if err != nil {
710
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
711
+ } else {
712
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
713
+ }
714
+ }(strengthStep, rowsPerLevel[i], i)
715
+ }
716
+
717
+ allResults := make([]LineupData, len(strengthVars))
718
+ var errors []error
719
+ successCount := 0
720
+
721
+ for i := 0; i < len(strengthVars); i++ {
722
+ result := <-resultsChan
723
+ if result.Error != nil {
724
+ errors = append(errors, result.Error)
725
+ } else {
726
+ allResults[result.Index] = result.Data
727
+ successCount++
728
+ }
729
+ }
730
+
731
+ if successCount == 0 {
732
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
733
+ }
734
+
735
+ var validResults []LineupData
736
+ for i, result := range allResults {
737
+ if len(result.Salary) > 0 {
738
+ validResults = append(validResults, result)
739
+ } else {
740
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
741
+ }
742
+ }
743
+
744
+ fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
745
+ return validResults, nil
746
+ }
747
+
748
+ func printResults(results []LineupData, nameMap map[int32]string) {
749
+ fmt.Printf("Generated %d strength levels:\n", len(results))
750
+
751
+ // Combine all results into one big dataset
752
+ var allSalaries []int32
753
+ var allProjections []float64
754
+ var allOwnership []float64
755
+ var allPlayers [][]int32
756
+
757
+ for _, result := range results {
758
+ allSalaries = append(allSalaries, result.Salary...)
759
+ allProjections = append(allProjections, result.Projection...)
760
+ allOwnership = append(allOwnership, result.Ownership...)
761
+ allPlayers = append(allPlayers, result.Players...)
762
+ }
763
+
764
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
765
+
766
+ if len(allSalaries) > 0 {
767
+ // Print top 5 lineups (highest projection)
768
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
769
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
770
+ playerNames := []string{
771
+ getPlayerName(allPlayers[i][0], nameMap, "PG1"),
772
+ getPlayerName(allPlayers[i][1], nameMap, "PG2"),
773
+ getPlayerName(allPlayers[i][2], nameMap, "SG1"),
774
+ getPlayerName(allPlayers[i][3], nameMap, "SG2"),
775
+ getPlayerName(allPlayers[i][4], nameMap, "SF1"),
776
+ getPlayerName(allPlayers[i][5], nameMap, "SF2"),
777
+ getPlayerName(allPlayers[i][6], nameMap, "PF1"),
778
+ getPlayerName(allPlayers[i][7], nameMap, "PF2"),
779
+ getPlayerName(allPlayers[i][8], nameMap, "C"),
780
+ }
781
+
782
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
783
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
784
+ fmt.Printf(" Players: PG1=%s, PG2=%s, SG1=%s, SG2=%s, SF1=%s, SF2=%s, PF1=%s, PF2=%s, C=%s\n",
785
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
786
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
787
+ }
788
+
789
+ // Print bottom 5 lineups (lowest projection)
790
+ if len(allSalaries) > 5 {
791
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
792
+ start := len(allSalaries) - 5
793
+ for i := start; i < len(allSalaries); i++ {
794
+ // Convert player IDs to names
795
+ playerNames := []string{
796
+ getPlayerName(allPlayers[i][0], nameMap, "PG1"),
797
+ getPlayerName(allPlayers[i][1], nameMap, "PG2"),
798
+ getPlayerName(allPlayers[i][2], nameMap, "SG1"),
799
+ getPlayerName(allPlayers[i][3], nameMap, "SG2"),
800
+ getPlayerName(allPlayers[i][4], nameMap, "SF1"),
801
+ getPlayerName(allPlayers[i][5], nameMap, "SF2"),
802
+ getPlayerName(allPlayers[i][6], nameMap, "PF1"),
803
+ getPlayerName(allPlayers[i][7], nameMap, "PF2"),
804
+ getPlayerName(allPlayers[i][8], nameMap, "C"),
805
+ }
806
+
807
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
808
+ i+1, allSalaries[i], allProjections[i], allOwnership[i])
809
+ fmt.Printf(" Players: PG1=%s, PG2=%s, SG1=%s, SG2=%s, SF1=%s, SF2=%s, PF1=%s, PF2=%s, C=%s\n",
810
+ playerNames[0], playerNames[1], playerNames[2], playerNames[3],
811
+ playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
812
+ }
813
+ }
814
+ }
815
+ }
816
+
817
+ func removeDuplicates(results []LineupData) []LineupData {
818
+ seen := make(map[string]bool)
819
+ var uniqueLineups []LineupData
820
+
821
+ for _, result := range results {
822
+ for i := 0; i < len(result.Players); i++ {
823
+ // Create combo string like Python
824
+ combo := fmt.Sprintf("%d%d%d%d%d%d%d%d%d",
825
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
826
+ result.Players[i][3], result.Players[i][4], result.Players[i][5],
827
+ result.Players[i][6], result.Players[i][7], result.Players[i][8])
828
+
829
+ // Sort combo like Python
830
+ sortedCombo := sortChars(combo)
831
+
832
+ if !seen[sortedCombo] {
833
+ seen[sortedCombo] = true
834
+ uniqueLineups = append(uniqueLineups, LineupData{
835
+ Salary: []int32{result.Salary[i]},
836
+ Projection: []float64{result.Projection[i]},
837
+ Team: []string{result.Team[i]},
838
+ Team_count: []int32{result.Team_count[i]},
839
+ Secondary: []string{result.Secondary[i]},
840
+ Secondary_count: []int32{result.Secondary_count[i]},
841
+ Ownership: []float64{result.Ownership[i]},
842
+ Players: [][]int32{result.Players[i]},
843
+ })
844
+ }
845
+ }
846
+ }
847
+
848
+ if len(uniqueLineups) == 0 {
849
+ return []LineupData{}
850
+ }
851
+
852
+ var allSalary []int32
853
+ var allProjection []float64
854
+ var allTeam []string
855
+ var allTeamCount []int32
856
+ var allSecondary []string
857
+ var allSecondaryCount []int32
858
+ var allOwnership []float64
859
+ var allPlayers [][]int32
860
+
861
+ for _, lineup := range uniqueLineups {
862
+ allSalary = append(allSalary, lineup.Salary[0])
863
+ allProjection = append(allProjection, lineup.Projection[0])
864
+ allTeam = append(allTeam, lineup.Team[0])
865
+ allTeamCount = append(allTeamCount, lineup.Team_count[0])
866
+ allSecondary = append(allSecondary, lineup.Secondary[0])
867
+ allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
868
+ allOwnership = append(allOwnership, lineup.Ownership[0])
869
+ allPlayers = append(allPlayers, lineup.Players[0])
870
+ }
871
+
872
+ return []LineupData{{
873
+ Salary: allSalary,
874
+ Projection: allProjection,
875
+ Team: allTeam,
876
+ Team_count: allTeamCount,
877
+ Secondary: allSecondary,
878
+ Secondary_count: allSecondaryCount,
879
+ Ownership: allOwnership,
880
+ Players: allPlayers,
881
+ }}
882
+ }
883
+
884
+ func connectToMongoDB() (*mongo.Client, error) {
885
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
886
+
887
+ clientOptions := options.Client().
888
+ ApplyURI(uri).
889
+ SetRetryWrites(true).
890
+ SetServerSelectionTimeout(10 * time.Second).
891
+ SetMaxPoolSize(100).
892
+ SetMinPoolSize(10).
893
+ SetMaxConnIdleTime(30 * time.Second).
894
+ SetRetryReads(true)
895
+
896
+ client, err := mongo.Connect(context.TODO(), clientOptions)
897
+ if err != nil {
898
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
899
+ }
900
+
901
+ err = client.Ping(context.TODO(), nil)
902
+ if err != nil {
903
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
904
+ }
905
+
906
+ fmt.Printf("Connected to MongoDB!")
907
+ return client, nil
908
+ }
909
+
910
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
911
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
912
+
913
+ collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
914
+ collection := db.Collection(collectionName)
915
+
916
+ err := collection.Drop(context.TODO())
917
+ if err != nil {
918
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
919
+ }
920
+
921
+ var documents []interface{}
922
+
923
+ for _, result := range results {
924
+
925
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
926
+ fmt.Printf("Warning: Empty result found, skipping\n")
927
+ continue
928
+ }
929
+
930
+ for i := 0; i < len(result.Salary); i++ {
931
+ if len(result.Players[i]) < 9 {
932
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 8\n", i, len(result.Players[i]))
933
+ }
934
+
935
+ doc := LineupDocument{
936
+ Salary: result.Salary[i],
937
+ Projection: result.Projection[i],
938
+ Team: result.Team[i],
939
+ Team_count: result.Team_count[i],
940
+ Secondary: result.Secondary[i],
941
+ Secondary_count: result.Secondary_count[i],
942
+ Ownership: result.Ownership[i],
943
+ PG1: result.Players[i][0],
944
+ PG2: result.Players[i][1],
945
+ SG1: result.Players[i][2],
946
+ SG2: result.Players[i][3],
947
+ SF1: result.Players[i][4],
948
+ SF2: result.Players[i][5],
949
+ PF1: result.Players[i][6],
950
+ PF2: result.Players[i][7],
951
+ C: result.Players[i][8],
952
+ CreatedAt: time.Now(),
953
+ }
954
+
955
+ documents = append(documents, doc)
956
+ }
957
+ }
958
+
959
+ if len(documents) == 0 {
960
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
961
+ }
962
+
963
+ if len(documents) > 500000 {
964
+ documents = documents[:500000]
965
+ }
966
+
967
+ chunkSize := 250000
968
+ for i := 0; i < len(documents); i += chunkSize {
969
+ end := i + chunkSize
970
+ if end > len(documents) {
971
+ end = len(documents)
972
+ }
973
+
974
+ chunk := documents[i:end]
975
+
976
+ for attempt := 0; attempt < 5; attempt++ {
977
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
978
+
979
+ opts := options.InsertMany().SetOrdered(false)
980
+ _, err := collection.InsertMany(ctx, chunk, opts)
981
+ cancel()
982
+
983
+ if err == nil {
984
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
985
+ break
986
+ }
987
+
988
+ fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
989
+ if attempt < 4 {
990
+ time.Sleep(1 * time.Second)
991
+ }
992
+ }
993
+
994
+ if err != nil {
995
+ return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
996
+ }
997
+ }
998
+
999
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
1000
+ return nil
1001
+ }
1002
+
1003
+ func groupPlayersBySlate(players []Player) map[string][]Player {
1004
+ slateGroups := make(map[string][]Player)
1005
+
1006
+ for _, player := range players {
1007
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
1008
+ }
1009
+
1010
+ return slateGroups
1011
+ }
1012
+
1013
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
1014
+ if name, exists := nameMap[playerID]; exists && name != "" {
1015
+ return name
1016
+ }
1017
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
1018
+ }
1019
+
1020
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
1021
+ nameMap := make(map[int32]string)
1022
+
1023
+ for keyStr, value := range playerSet.Maps.NameMap {
1024
+ key, err := strconv.Atoi(keyStr)
1025
+ if err != nil {
1026
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
1027
+ continue
1028
+ }
1029
+ nameMap[int32(key)] = value
1030
+ }
1031
+
1032
+ return nameMap
1033
+ }
1034
+
1035
+ func main() {
1036
+ site := "FD"
1037
+ sport := "NBA"
1038
+ if len(os.Args) > 1 {
1039
+ site = os.Args[1]
1040
+ }
1041
+ if len(os.Args) > 2 {
1042
+ sport = os.Args[2]
1043
+ }
1044
+ processedData, err := loadPlayerData()
1045
+ if err != nil {
1046
+ fmt.Printf("Error loading data: %v\n", err)
1047
+ return
1048
+ }
1049
+
1050
+ start := time.Now()
1051
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
1052
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
1053
+
1054
+ SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
1055
+
1056
+ salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
1057
+
1058
+ nameMap := convertNamesToMaps(&processedData.PlayersMedian)
1059
+
1060
+ mongoClient, err := connectToMongoDB()
1061
+ if err != nil {
1062
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
1063
+ return
1064
+ }
1065
+ defer func() {
1066
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
1067
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
1068
+ }
1069
+ }()
1070
+
1071
+ optimalsBySlate, err := loadOptimals()
1072
+ if err != nil {
1073
+ fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
1074
+ optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
1075
+ } else {
1076
+ totalOptimals := 0
1077
+ for _, optimals := range optimalsBySlate {
1078
+ totalOptimals += len(optimals.Salary)
1079
+ }
1080
+ fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
1081
+ }
1082
+
1083
+ for slate, players := range SlateGroups {
1084
+
1085
+ fmt.Printf("Processing slate: %s\n", slate)
1086
+
1087
+ results, err := runSeedframeRoutines(
1088
+ players, strengthVars, rowsPerLevel,
1089
+ salaryMapJSON, projectionMapJSON,
1090
+ ownershipMapJSON, teamMapJSON, site)
1091
+
1092
+ if err != nil {
1093
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
1094
+ continue
1095
+ }
1096
+
1097
+ // Get optimal lineups for this specific slate
1098
+ slateOptimals := optimalsBySlate[slate]
1099
+
1100
+ // Append optimal lineups for this slate
1101
+ finalResults := appendOptimalLineups(results, slateOptimals)
1102
+
1103
+ exportResults := removeDuplicates(finalResults)
1104
+
1105
+ exportResults[0] = sortDataByField(exportResults[0], "projection", false)
1106
+
1107
+ err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
1108
+ if err != nil {
1109
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
1110
+ continue
1111
+ }
1112
+
1113
+ printResults(exportResults, nameMap)
1114
+ }
1115
+
1116
+ // Add this line at the end
1117
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
1118
+ }
func/showdown_go/showdown_seed_frames.go ADDED
@@ -0,0 +1,1006 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ CptMedian PlayerSet `json:"cpt_median"`
62
+ }
63
+
64
+ type PlayerData struct {
65
+ Players []Player
66
+ NameMap map[int]string
67
+ }
68
+
69
+ type StrengthResult struct {
70
+ Index int
71
+ Data LineupData
72
+ Error error
73
+ }
74
+
75
+ type LineupDocument struct {
76
+ Salary int32 `bson:"salary"`
77
+ Projection float64 `bson:"proj"`
78
+ Team string `bson:"Team"`
79
+ Team_count int32 `bson:"Team_count"`
80
+ Secondary string `bson:"Secondary"`
81
+ Secondary_count int32 `bson:"Secondary_count"`
82
+ Ownership float64 `bson:"Own"`
83
+ CPT string `bson:"CPT"`
84
+ FLEX1 string `bson:"FLEX1"`
85
+ FLEX2 string `bson:"FLEX2"`
86
+ FLEX3 string `bson:"FLEX3"`
87
+ FLEX4 string `bson:"FLEX4"`
88
+ FLEX5 string `bson:"FLEX5"`
89
+ CreatedAt time.Time `bson:"created_at"`
90
+ }
91
+
92
+ func loadPlayerData() (*ProcessedData, error) {
93
+ data, err := ioutil.ReadFile("showdown_go/player_data.json")
94
+ if err != nil {
95
+ return nil, fmt.Errorf("failed to read in data: %v", err)
96
+ }
97
+
98
+ var processedData ProcessedData
99
+ if err := json.Unmarshal(data, &processedData); err != nil {
100
+ return nil, fmt.Errorf("failed to parse json: %v", err)
101
+ }
102
+
103
+ return &processedData, nil
104
+ }
105
+
106
+ func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
107
+ salaryMap := make(map[int32]int32)
108
+ projMap := make(map[int32]float64)
109
+ ownMap := make(map[int32]float64)
110
+ teamMap := make(map[int32]string)
111
+
112
+ for keyStr, value := range playerSet.Maps.SalaryMap {
113
+ key, err := strconv.Atoi(keyStr)
114
+ if err != nil {
115
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
116
+ continue
117
+ }
118
+ salaryMap[int32(key)] = value
119
+ }
120
+
121
+ for keyStr, value := range playerSet.Maps.ProjectionMap {
122
+ key, err := strconv.Atoi(keyStr)
123
+ if err != nil {
124
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
125
+ continue
126
+ }
127
+ projMap[int32(key)] = value
128
+ }
129
+
130
+ for keyStr, value := range playerSet.Maps.OwnershipMap {
131
+ key, err := strconv.Atoi(keyStr)
132
+ if err != nil {
133
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
134
+ continue
135
+ }
136
+ ownMap[int32(key)] = value
137
+ }
138
+
139
+ for keyStr, value := range playerSet.Maps.TeamMap {
140
+ key, err := strconv.Atoi(keyStr)
141
+ if err != nil {
142
+ fmt.Printf("Error converting key %s: %v\n", keyStr, err)
143
+ continue
144
+ }
145
+ teamMap[int32(key)] = value
146
+ }
147
+
148
+ return salaryMap, projMap, ownMap, teamMap
149
+ }
150
+
151
+ func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
152
+ result := make([]U, len(input))
153
+
154
+ for i, key := range input {
155
+ if value, exists := valueMap[key]; exists {
156
+ result[i] = value
157
+ } else {
158
+ var zero U
159
+ result[i] = zero
160
+ }
161
+ }
162
+
163
+ return result
164
+ }
165
+
166
+ func sortChars(strData string) string {
167
+ runes := []rune(strData)
168
+ slices.Sort(runes)
169
+ return string(runes)
170
+ }
171
+
172
+ func rowMostCommon(row []int) (*int, *int) {
173
+ if len(row) == 0 {
174
+ return nil, nil
175
+ }
176
+
177
+ counts := make(map[int]int)
178
+ for _, value := range row {
179
+ counts[value]++
180
+ }
181
+
182
+ if len(counts) < 2 {
183
+ return nil, nil
184
+ }
185
+
186
+ mostCommon := 0
187
+ maxCount := 0
188
+ secondMost := 0
189
+ secondMax := 0
190
+
191
+ for value, count := range counts {
192
+ if count > maxCount {
193
+ secondMax = maxCount
194
+ secondMost = mostCommon
195
+
196
+ maxCount = count
197
+ mostCommon = value
198
+ } else if count > secondMax && count < maxCount {
199
+ secondMax = count
200
+ secondMost = value
201
+ }
202
+
203
+ }
204
+
205
+ return &mostCommon, &secondMost
206
+ }
207
+
208
+ func rowBiggestAndSecond(row []int) (int, int) {
209
+ if len(row) == 0 {
210
+ return 0, 0
211
+ }
212
+
213
+ counts := make(map[int]int)
214
+ for _, value := range row {
215
+ counts[value]++
216
+ }
217
+
218
+ if len(counts) == 1 {
219
+ return len(row), 0
220
+ }
221
+
222
+ biggestVal := 0
223
+ secondBiggestVal := 0
224
+
225
+ for _, count := range counts {
226
+ if count > biggestVal {
227
+ secondBiggestVal = biggestVal
228
+ biggestVal = count
229
+ } else if count > secondBiggestVal && count < biggestVal {
230
+ secondBiggestVal = count
231
+ }
232
+ }
233
+
234
+ return biggestVal, secondBiggestVal
235
+ }
236
+
237
+ func createOverallDFs(players []Player, pos string) PlayerData {
238
+ var filteredPlayers []Player
239
+ for _, player := range players {
240
+ if strings.Contains(player.Position, pos) {
241
+ filteredPlayers = append(filteredPlayers, player)
242
+ }
243
+ }
244
+
245
+ nameMap := make(map[int]string)
246
+ for i, player := range filteredPlayers {
247
+ nameMap[i] = player.Name
248
+ }
249
+
250
+ return PlayerData{
251
+ Players: filteredPlayers,
252
+ NameMap: nameMap,
253
+ }
254
+ }
255
+
256
+ func sumSalaryRows(data [][]int32) []int32 {
257
+ result := make([]int32, len(data))
258
+
259
+ for i, row := range data {
260
+ var sum int32
261
+ for _, value := range row {
262
+ sum += value
263
+ }
264
+ result[i] = sum
265
+ }
266
+
267
+ return result
268
+ }
269
+
270
+ func sumOwnRows(data [][]float64) []float64 {
271
+ result := make([]float64, len(data))
272
+
273
+ for i, row := range data {
274
+ var sum float64
275
+ for _, value := range row {
276
+ sum += value
277
+ }
278
+ result[i] = sum
279
+ }
280
+
281
+ return result
282
+ }
283
+
284
+ func sumProjRows(data [][]float64) []float64 {
285
+ result := make([]float64, len(data))
286
+
287
+ for i, row := range data {
288
+ var sum float64
289
+ for _, value := range row {
290
+ sum += value
291
+ }
292
+ result[i] = sum
293
+ }
294
+
295
+ return result
296
+ }
297
+
298
+ func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
299
+ var validIndicies []int
300
+
301
+ for i, value := range values {
302
+ if value <= maxVal {
303
+ validIndicies = append(validIndicies, i)
304
+ }
305
+ }
306
+
307
+ return validIndicies
308
+ }
309
+
310
+ func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
311
+ var validIndicies []int
312
+
313
+ for i, value := range values {
314
+ if value >= minVal {
315
+ validIndicies = append(validIndicies, i)
316
+ }
317
+ }
318
+
319
+ return validIndicies
320
+ }
321
+
322
+ func sliceByIndicies[T any](data []T, indicies []int) []T {
323
+ result := make([]T, len(indicies))
324
+
325
+ for i, idx := range indicies {
326
+ result[i] = data[idx]
327
+ }
328
+
329
+ return result
330
+ }
331
+
332
+ func sortDataByField(data LineupData, field string, ascending bool) LineupData {
333
+ indicies := make([]int, len(data.Ownership))
334
+ for i := range indicies {
335
+ indicies[i] = i
336
+ }
337
+
338
+ switch field {
339
+ case "salary":
340
+ sort.Slice(indicies, func(i, j int) bool {
341
+ if ascending {
342
+ return data.Salary[indicies[i]] < data.Salary[indicies[j]]
343
+ }
344
+ return data.Salary[indicies[i]] > data.Salary[indicies[j]]
345
+ })
346
+ case "projection":
347
+ sort.Slice(indicies, func(i, j int) bool {
348
+ if ascending {
349
+ return data.Projection[indicies[i]] < data.Projection[indicies[j]]
350
+ }
351
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
352
+ })
353
+ case "ownership":
354
+ sort.Slice(indicies, func(i, j int) bool {
355
+ if ascending {
356
+ return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
357
+ }
358
+ return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
359
+ })
360
+ default:
361
+ sort.Slice(indicies, func(i, j int) bool {
362
+ return data.Projection[indicies[i]] > data.Projection[indicies[j]]
363
+ })
364
+ }
365
+
366
+ return LineupData{
367
+ Salary: sliceByIndicies(data.Salary, indicies),
368
+ Projection: sliceByIndicies(data.Projection, indicies),
369
+ Team: sliceByIndicies(data.Team, indicies),
370
+ Team_count: sliceByIndicies(data.Team_count, indicies),
371
+ Secondary: sliceByIndicies(data.Secondary, indicies),
372
+ Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
373
+ Ownership: sliceByIndicies(data.Ownership, indicies),
374
+ Players: sliceByIndicies(data.Players, indicies),
375
+ }
376
+ }
377
+
378
+ func combineArrays(flex1, flex2, flex3, flex4, flex5, flex6 []int32) [][]int32 {
379
+ length := len(flex1)
380
+
381
+ result := make([][]int32, length)
382
+
383
+ for i := 0; i < length; i++ {
384
+ result[i] = []int32{
385
+ flex1[i],
386
+ flex2[i],
387
+ flex3[i],
388
+ flex4[i],
389
+ flex5[i],
390
+ flex6[i],
391
+ }
392
+ }
393
+
394
+ return result
395
+ }
396
+
397
+ func createSeedFrames(combinedArrays [][]int32, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) LineupData {
398
+
399
+ salaries := make([][]int32, len(combinedArrays))
400
+ projections := make([][]float64, len(combinedArrays))
401
+ ownership := make([][]float64, len(combinedArrays))
402
+ teamArrays := make([][]string, len(combinedArrays))
403
+
404
+ for i, row := range combinedArrays {
405
+
406
+ cptPlayers := []int32{row[0]}
407
+ flexPlayers := row[1:]
408
+
409
+ cptSalaries := processAndFill(cptPlayers, cptSalaryMap)
410
+ cptProjections := processAndFill(cptPlayers, cptProjMap)
411
+ cptOwnership := processAndFill(cptPlayers, cptOwnMap)
412
+ cptTeams := processAndFill(cptPlayers, cptTeamMap)
413
+
414
+ flexSalaries := processAndFill(flexPlayers, flexSalaryMap)
415
+ flexProjections := processAndFill(flexPlayers, flexProjMap)
416
+ flexOwnership := processAndFill(flexPlayers, flexOwnMap)
417
+ flexTeams := processAndFill(flexPlayers, flexTeamMap)
418
+
419
+ salaries[i] = append(cptSalaries, flexSalaries...)
420
+ projections[i] = append(cptProjections, flexProjections...)
421
+ ownership[i] = append(cptOwnership, flexOwnership...)
422
+ teamArrays[i] = append(cptTeams, flexTeams...)
423
+ }
424
+
425
+ totalSalaries := sumSalaryRows(salaries)
426
+ totalProjections := sumProjRows(projections)
427
+ totalOwnership := sumOwnRows(ownership)
428
+
429
+ teamData := make([]string, len(teamArrays))
430
+ teamCounts := make([]int32, len(teamArrays))
431
+ secondaryData := make([]string, len(teamArrays))
432
+ secondaryCounts := make([]int32, len(teamArrays))
433
+
434
+ for i, teams := range teamArrays {
435
+ teamMap := make(map[string]int)
436
+ uniqueTeams := make([]string, 0)
437
+ for _, team := range teams {
438
+ if _, exists := teamMap[team]; !exists {
439
+ teamMap[team] = len(uniqueTeams)
440
+ uniqueTeams = append(uniqueTeams, team)
441
+ }
442
+ }
443
+
444
+ teamInts := make([]int, len(teams))
445
+ for j, team := range teams {
446
+ teamInts[j] = teamMap[team]
447
+ }
448
+
449
+ mostCommon, secondMost := rowMostCommon(teamInts)
450
+ if mostCommon != nil {
451
+ teamData[i] = uniqueTeams[*mostCommon]
452
+ teamCount, _ := rowBiggestAndSecond(teamInts)
453
+ teamCounts[i] = int32(teamCount)
454
+ }
455
+ if secondMost != nil {
456
+ secondaryData[i] = uniqueTeams[*secondMost]
457
+ _, secondaryCount := rowBiggestAndSecond(teamInts)
458
+ secondaryCounts[i] = int32(secondaryCount)
459
+ }
460
+ }
461
+
462
+ var validIndicies []int
463
+ if site == "DK" {
464
+ validIndicies = filterMax(totalSalaries, int32(50000))
465
+ } else {
466
+ validIndicies = filterMax(totalSalaries, int32(60000))
467
+ }
468
+
469
+ validData := LineupData{
470
+ Salary: sliceByIndicies(totalSalaries, validIndicies),
471
+ Projection: sliceByIndicies(totalProjections, validIndicies),
472
+ Team: sliceByIndicies(teamData, validIndicies),
473
+ Team_count: sliceByIndicies(teamCounts, validIndicies),
474
+ Secondary: sliceByIndicies(secondaryData, validIndicies),
475
+ Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
476
+ Ownership: sliceByIndicies(totalOwnership, validIndicies),
477
+ Players: sliceByIndicies(combinedArrays, validIndicies),
478
+ }
479
+
480
+ return sortDataByField(validData, "ownership", false)
481
+ }
482
+
483
+ func calculateQuantile(values []float64, quantile float64) (float64, error) {
484
+ if len(values) == 0 {
485
+ return 0, fmt.Errorf("cannot calculate quantile of empty slice")
486
+ }
487
+
488
+ if quantile < 0 || quantile > 1 {
489
+ return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
490
+ }
491
+
492
+ sorted := make([]float64, len(values))
493
+ copy(sorted, values)
494
+ sort.Float64s(sorted)
495
+
496
+ index := int(float64(len(sorted)-1) * quantile)
497
+ return sorted[index], nil
498
+ }
499
+
500
+ func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
501
+ if count > len(playerIDs) {
502
+ return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
503
+ }
504
+
505
+ shuffled := make([]int32, len(playerIDs))
506
+ copy(shuffled, playerIDs)
507
+
508
+ for i := len(shuffled) - 1; i > 0; i-- {
509
+ j := rng.Intn(i + 1)
510
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
511
+ }
512
+
513
+ return shuffled[:count], nil
514
+ }
515
+
516
+ func generateBaseArrays(cptPlayers, flexPlayers []Player, numRows int, rng *rand.Rand) ([][]int32, error) {
517
+
518
+ if len(cptPlayers) < 1 || len(flexPlayers) < 5 {
519
+ return nil, fmt.Errorf("need at least 1 cpt and 5 flex, got %d cpt and %d flex", len(cptPlayers), len(flexPlayers))
520
+ }
521
+
522
+ var validArrays [][]int32
523
+ attempts := 0
524
+ maxAttempts := numRows * 10
525
+
526
+ for len(validArrays) < numRows && attempts < maxAttempts {
527
+ attempts++
528
+
529
+ cptPlayer := cptPlayers[rng.Intn(len(cptPlayers))]
530
+ flexPlayers_selected := make([]Player, 5)
531
+
532
+ for i := 0; i < 5; i++ {
533
+ flexPlayers_selected[i] = flexPlayers[rng.Intn(len(flexPlayers))]
534
+ }
535
+
536
+ flexNames := make([]string, 5)
537
+ for i, player := range flexPlayers_selected {
538
+ flexNames[i] = player.Name
539
+ }
540
+
541
+ if flexNames[0] != flexNames[1] && flexNames[0] != flexNames[2] && flexNames[0] != flexNames[3] && flexNames[0] != flexNames[4] &&
542
+ flexNames[1] != flexNames[2] && flexNames[1] != flexNames[3] && flexNames[1] != flexNames[4] &&
543
+ flexNames[2] != flexNames[3] && flexNames[2] != flexNames[4] &&
544
+ flexNames[3] != flexNames[4] {
545
+
546
+ cptUnique := true
547
+ for _, flexName := range flexNames {
548
+ if cptPlayer.Name == flexName {
549
+ cptUnique = false
550
+ break
551
+ }
552
+ }
553
+
554
+ if cptUnique {
555
+ flexIDs := make([]int32, 5)
556
+ for i, player := range flexPlayers_selected {
557
+ flexIDs[i] = player.ID
558
+ }
559
+ combinedRow := append([]int32{cptPlayer.ID}, flexIDs...)
560
+ validArrays = append(validArrays, combinedRow)
561
+ }
562
+ }
563
+ }
564
+
565
+ if len(validArrays) == 0 {
566
+ return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
567
+ }
568
+
569
+ return validArrays, nil
570
+ }
571
+
572
+ func filterPlayersInQuantile(players []Player, strengthStep float64) ([]Player, error) {
573
+ if len(players) == 0 {
574
+ return nil, fmt.Errorf("no players provided")
575
+ }
576
+
577
+ ownVals := make([]float64, len(players))
578
+ for i, player := range players {
579
+ ownVals[i] = player.OwnValue
580
+ }
581
+
582
+ threshold, err := calculateQuantile(ownVals, strengthStep)
583
+ if err != nil {
584
+ return nil, err
585
+ }
586
+
587
+ var filtered []Player
588
+ for _, player := range players {
589
+ if player.OwnValue >= threshold {
590
+ filtered = append(filtered, player)
591
+ }
592
+ }
593
+
594
+ if len(filtered) == 0 {
595
+ return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
596
+ }
597
+
598
+ return filtered, nil
599
+ }
600
+
601
+ func processStrengthLevels(cptPlayers, flexPlayers []Player, strengthStep float64, numRows int, rng *rand.Rand, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) (LineupData, error) {
602
+
603
+ filteredCptPlayers, err := filterPlayersInQuantile(cptPlayers, strengthStep)
604
+ if err != nil {
605
+ return LineupData{}, err
606
+ }
607
+
608
+ filteredFlexPlayers, err := filterPlayersInQuantile(flexPlayers, strengthStep)
609
+ if err != nil {
610
+ return LineupData{}, err
611
+ }
612
+
613
+ overallArrays, err := generateBaseArrays(filteredCptPlayers, filteredFlexPlayers, numRows, rng)
614
+ if err != nil {
615
+ return LineupData{}, err
616
+ }
617
+
618
+ result := createSeedFrames(overallArrays, cptSalaryMap, flexSalaryMap, cptProjMap, flexProjMap, cptOwnMap, flexOwnMap, cptTeamMap, flexTeamMap, site)
619
+
620
+ return result, nil
621
+ }
622
+
623
+ func runSeedframeRoutines(cptPlayers, flexPlayers []Player, strengthVars []float64, rowsPerLevel []int, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) ([]LineupData, error) {
624
+ resultsChan := make(chan StrengthResult, len(strengthVars))
625
+
626
+ for i, strengthStep := range strengthVars {
627
+ go func(step float64, rows int, index int) {
628
+ rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
629
+
630
+ result, err := processStrengthLevels(cptPlayers, flexPlayers, step, rows, rng, cptSalaryMap, flexSalaryMap, cptProjMap, flexProjMap, cptOwnMap, flexOwnMap, cptTeamMap, flexTeamMap, site)
631
+ resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
632
+
633
+ if err != nil {
634
+ fmt.Printf("Error in strength level %.2f: %v\n", step, err)
635
+ } else {
636
+ fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
637
+ }
638
+ }(strengthStep, rowsPerLevel[i], i)
639
+ }
640
+
641
+ allResults := make([]LineupData, len(strengthVars))
642
+ var errors []error
643
+ successCount := 0
644
+
645
+ for i := 0; i < len(strengthVars); i++ {
646
+ result := <-resultsChan
647
+ if result.Error != nil {
648
+ errors = append(errors, result.Error)
649
+ } else {
650
+ allResults[result.Index] = result.Data
651
+ successCount++
652
+ }
653
+ }
654
+
655
+ if successCount == 0 {
656
+ return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
657
+ }
658
+
659
+ var validResults []LineupData
660
+ for i, result := range allResults {
661
+ if len(result.Salary) > 0 {
662
+ validResults = append(validResults, result)
663
+ } else {
664
+ fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
665
+ }
666
+ }
667
+
668
+ fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
669
+ return validResults, nil
670
+ }
671
+
672
+ func printResults(results []LineupData) {
673
+ fmt.Printf("Generated %d strength levels:\n", len(results))
674
+
675
+ // Combine all results into one big dataset
676
+ var allSalaries []int32
677
+ var allProjections []float64
678
+ var allOwnership []float64
679
+ var allPlayers [][]int32
680
+
681
+ for _, result := range results {
682
+ allSalaries = append(allSalaries, result.Salary...)
683
+ allProjections = append(allProjections, result.Projection...)
684
+ allOwnership = append(allOwnership, result.Ownership...)
685
+ allPlayers = append(allPlayers, result.Players...)
686
+ }
687
+
688
+ fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
689
+
690
+ if len(allSalaries) > 0 {
691
+ // Print top 5 lineups (highest projection)
692
+ fmt.Printf("\nTop 5 lineups (by projection):\n")
693
+ for i := 0; i < 5 && i < len(allSalaries); i++ {
694
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f, Players=%v\n",
695
+ i+1, allSalaries[i], allProjections[i], allOwnership[i], allPlayers[i])
696
+ }
697
+
698
+ // Print bottom 5 lineups (lowest projection)
699
+ if len(allSalaries) > 5 {
700
+ fmt.Printf("\nBottom 5 lineups (by projection):\n")
701
+ start := len(allSalaries) - 5
702
+ for i := start; i < len(allSalaries); i++ {
703
+ fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f, Players=%v\n",
704
+ i+1, allSalaries[i], allProjections[i], allOwnership[i], allPlayers[i])
705
+ }
706
+ }
707
+ }
708
+ }
709
+
710
+ func removeDuplicates(results []LineupData) []LineupData {
711
+ seen := make(map[string]bool)
712
+ var uniqueLineups []LineupData
713
+
714
+ for _, result := range results {
715
+ for i := 0; i < len(result.Players); i++ {
716
+ // Create combo string like Python
717
+ combo := fmt.Sprintf("%d%d%d%d%d%d",
718
+ result.Players[i][0], result.Players[i][1], result.Players[i][2],
719
+ result.Players[i][3], result.Players[i][4], result.Players[i][5])
720
+
721
+ // Sort combo like Python
722
+ sortedCombo := sortChars(combo)
723
+
724
+ if !seen[sortedCombo] {
725
+ seen[sortedCombo] = true
726
+ uniqueLineups = append(uniqueLineups, LineupData{
727
+ Salary: []int32{result.Salary[i]},
728
+ Projection: []float64{result.Projection[i]},
729
+ Team: []string{result.Team[i]},
730
+ Team_count: []int32{result.Team_count[i]},
731
+ Secondary: []string{result.Secondary[i]},
732
+ Secondary_count: []int32{result.Secondary_count[i]},
733
+ Ownership: []float64{result.Ownership[i]},
734
+ Players: [][]int32{result.Players[i]},
735
+ })
736
+ }
737
+ }
738
+ }
739
+
740
+ if len(uniqueLineups) == 0 {
741
+ return []LineupData{}
742
+ }
743
+
744
+ var allSalary []int32
745
+ var allProjection []float64
746
+ var allTeam []string
747
+ var allTeamCount []int32
748
+ var allSecondary []string
749
+ var allSecondaryCount []int32
750
+ var allOwnership []float64
751
+ var allPlayers [][]int32
752
+
753
+ for _, lineup := range uniqueLineups {
754
+ allSalary = append(allSalary, lineup.Salary[0])
755
+ allProjection = append(allProjection, lineup.Projection[0])
756
+ allTeam = append(allTeam, lineup.Team[0])
757
+ allTeamCount = append(allTeamCount, lineup.Team_count[0])
758
+ allSecondary = append(allSecondary, lineup.Secondary[0])
759
+ allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
760
+ allOwnership = append(allOwnership, lineup.Ownership[0])
761
+ allPlayers = append(allPlayers, lineup.Players[0])
762
+ }
763
+
764
+ return []LineupData{{
765
+ Salary: allSalary,
766
+ Projection: allProjection,
767
+ Team: allTeam,
768
+ Team_count: allTeamCount,
769
+ Secondary: allSecondary,
770
+ Secondary_count: allSecondaryCount,
771
+ Ownership: allOwnership,
772
+ Players: allPlayers,
773
+ }}
774
+ }
775
+
776
+ func connectToMongoDB() (*mongo.Client, error) {
777
+ uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
778
+
779
+ clientOptions := options.Client().
780
+ ApplyURI(uri).
781
+ SetRetryWrites(true).
782
+ SetServerSelectionTimeout(10 * time.Second).
783
+ SetMaxPoolSize(100).
784
+ SetMinPoolSize(10).
785
+ SetMaxConnIdleTime(30 * time.Second).
786
+ SetRetryReads(true)
787
+
788
+ client, err := mongo.Connect(context.TODO(), clientOptions)
789
+ if err != nil {
790
+ return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
791
+ }
792
+
793
+ err = client.Ping(context.TODO(), nil)
794
+ if err != nil {
795
+ return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
796
+ }
797
+
798
+ fmt.Printf("Connected to MongoDB!")
799
+ return client, nil
800
+ }
801
+
802
+ func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, cptNameMap, flexNameMap map[int32]string, site string, sport string) error {
803
+ db := client.Database(fmt.Sprintf("%s_Database", sport))
804
+
805
+ collectionName := fmt.Sprintf("%s_%s_SD_seed_frame_%s", site, sport, slate)
806
+ collection := db.Collection(collectionName)
807
+
808
+ err := collection.Drop(context.TODO())
809
+ if err != nil {
810
+ fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
811
+ }
812
+
813
+ var documents []interface{}
814
+
815
+ for _, result := range results {
816
+
817
+ if len(result.Salary) == 0 || len(result.Players) == 0 {
818
+ fmt.Printf("Warning: Empty result found, skipping\n")
819
+ continue
820
+ }
821
+
822
+ for i := 0; i < len(result.Salary); i++ {
823
+ if len(result.Players[i]) < 6 {
824
+ fmt.Printf("Warning: Lineup %d has only %d players, expected 6\n", i, len(result.Players[i]))
825
+ }
826
+
827
+ playerNames := []string{
828
+ getPlayerName(result.Players[i][0], cptNameMap, "CPT"), // CPT
829
+ getPlayerName(result.Players[i][1], flexNameMap, "FLEX"), // FLEX1
830
+ getPlayerName(result.Players[i][2], flexNameMap, "FLEX"), // FLEX2
831
+ getPlayerName(result.Players[i][3], flexNameMap, "FLEX"), // FLEX3
832
+ getPlayerName(result.Players[i][4], flexNameMap, "FLEX"), // FLEX4
833
+ getPlayerName(result.Players[i][5], flexNameMap, "FLEX"), // FLEX5
834
+ }
835
+
836
+ doc := LineupDocument{
837
+ Salary: result.Salary[i],
838
+ Projection: result.Projection[i],
839
+ Team: result.Team[i],
840
+ Team_count: result.Team_count[i],
841
+ Secondary: result.Secondary[i],
842
+ Secondary_count: result.Secondary_count[i],
843
+ Ownership: result.Ownership[i],
844
+ CPT: playerNames[0],
845
+ FLEX1: playerNames[1],
846
+ FLEX2: playerNames[2],
847
+ FLEX3: playerNames[3],
848
+ FLEX4: playerNames[4],
849
+ FLEX5: playerNames[5],
850
+ CreatedAt: time.Now(),
851
+ }
852
+ documents = append(documents, doc)
853
+ }
854
+ }
855
+
856
+ if len(documents) == 0 {
857
+ fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
858
+ }
859
+
860
+ if len(documents) > 500000 {
861
+ documents = documents[:500000]
862
+ }
863
+
864
+ chunkSize := 250000
865
+ for i := 0; i < len(documents); i += chunkSize {
866
+ end := i + chunkSize
867
+ if end > len(documents) {
868
+ end = len(documents)
869
+ }
870
+
871
+ chunk := documents[i:end]
872
+
873
+ for attempt := 0; attempt < 5; attempt++ {
874
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
875
+
876
+ opts := options.InsertMany().SetOrdered(false)
877
+ _, err := collection.InsertMany(ctx, chunk, opts)
878
+ cancel()
879
+
880
+ if err == nil {
881
+ fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
882
+ break
883
+ }
884
+
885
+ fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
886
+ if attempt < 4 {
887
+ time.Sleep(1 * time.Second)
888
+ }
889
+ }
890
+
891
+ if err != nil {
892
+ return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
893
+ }
894
+ }
895
+
896
+ fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
897
+ return nil
898
+ }
899
+
900
+ func groupPlayersBySlate(players []Player) map[string][]Player {
901
+ slateGroups := make(map[string][]Player)
902
+
903
+ for _, player := range players {
904
+ slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
905
+ }
906
+
907
+ return slateGroups
908
+ }
909
+
910
+ func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
911
+ if name, exists := nameMap[playerID]; exists && name != "" {
912
+ return name
913
+ }
914
+ return fmt.Sprintf("Unknown_%s_%d", position, playerID)
915
+ }
916
+
917
+ func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
918
+ nameMap := make(map[int32]string)
919
+
920
+ for keyStr, value := range playerSet.Maps.NameMap {
921
+ key, err := strconv.Atoi(keyStr)
922
+ if err != nil {
923
+ fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
924
+ continue
925
+ }
926
+ nameMap[int32(key)] = value
927
+ }
928
+
929
+ return nameMap
930
+ }
931
+
932
+ func main() {
933
+ site := "DK"
934
+ sport := "NFL"
935
+ if len(os.Args) > 1 {
936
+ site = os.Args[1]
937
+ }
938
+ if len(os.Args) > 2 {
939
+ sport = os.Args[2]
940
+ }
941
+
942
+ processedData, err := loadPlayerData()
943
+ if err != nil {
944
+ fmt.Printf("Error loading data: %v\n", err)
945
+ return
946
+ }
947
+
948
+ start := time.Now()
949
+ strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
950
+ rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
951
+
952
+ cptSlateGroups := groupPlayersBySlate(processedData.CptMedian.Players)
953
+ flexSlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
954
+
955
+ cptSalaryMapJSON, cptProjectionMapJSON, cptOwnershipMapJSON, cptTeamMapJSON := convertMapsToInt32Keys(&processedData.CptMedian)
956
+ flexSalaryMapJSON, flexProjectionMapJSON, flexOwnershipMapJSON, flexTeamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
957
+
958
+ cptNameMap := convertNamesToMaps(&processedData.CptMedian)
959
+ flexNameMap := convertNamesToMaps(&processedData.PlayersMedian)
960
+
961
+ mongoClient, err := connectToMongoDB()
962
+ if err != nil {
963
+ fmt.Printf("Error connecting to MongoDB: %v\n", err)
964
+ return
965
+ }
966
+ defer func() {
967
+ if err := mongoClient.Disconnect(context.TODO()); err != nil {
968
+ fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
969
+ }
970
+ }()
971
+
972
+ for slate, cptPlayers := range cptSlateGroups {
973
+ flexPlayers, exists := flexSlateGroups[slate]
974
+ if !exists {
975
+ fmt.Printf("Warning: no FLEX players found for slate %s\n", slate)
976
+ continue
977
+ }
978
+
979
+ fmt.Printf("Processing slate: %s\n", slate)
980
+
981
+ results, err := runSeedframeRoutines(
982
+ cptPlayers, flexPlayers,
983
+ strengthVars, rowsPerLevel,
984
+ cptSalaryMapJSON, flexSalaryMapJSON,
985
+ cptProjectionMapJSON, flexProjectionMapJSON,
986
+ cptOwnershipMapJSON, flexOwnershipMapJSON,
987
+ cptTeamMapJSON, flexTeamMapJSON, site)
988
+
989
+ if err != nil {
990
+ fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
991
+ continue
992
+ }
993
+
994
+ uniqueResults := removeDuplicates(results)
995
+ fmt.Printf("Generated %d unique lineups for slate %s\n", len(uniqueResults), slate)
996
+
997
+ err = insertLineupsToMongoDB(mongoClient, uniqueResults, slate, cptNameMap, flexNameMap, site, sport)
998
+ if err != nil {
999
+ fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
1000
+ continue
1001
+ }
1002
+ }
1003
+
1004
+ // Add this line at the end
1005
+ fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
1006
+ }
requirements.txt CHANGED
@@ -6,3 +6,6 @@ ortools
6
  gspread
7
  discordwebhook
8
  pymongo
 
 
 
 
6
  gspread
7
  discordwebhook
8
  pymongo
9
+ xgboost
10
+ lightgbm
11
+ scikit-learn
src/database.py CHANGED
@@ -52,9 +52,12 @@ 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
 
56
  # Google Sheets URL
57
  NHL_Master_hold = get_secret("MASTER_SHEET_URL", 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863')
 
58
 
59
  # Discord Webhook
60
  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
+ nba_db = client['NBA_Database']
56
+ contest_db = client["Contest_Information"]
57
 
58
  # Google Sheets URL
59
  NHL_Master_hold = get_secret("MASTER_SHEET_URL", 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863')
60
+ NBA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-bD6_u9pqrYE6Yrlbb_wEkmH-ot0/edit?gid=172632260#gid=172632260'
61
 
62
  # Discord Webhook
63
  discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
src/sports/nba_functions.py CHANGED
The diff for this file is too large to render. See raw diff
 
src/sports/nhl_functions.py CHANGED
@@ -1,22 +1,10 @@
1
  import time
2
- import sys
3
- import io
4
- from contextlib import redirect_stdout
5
  import re
6
 
7
  # Numpy
8
  from numpy import where as np_where
9
- from numpy import newaxis as np_newaxis
10
  from numpy import random as np_random
11
- from numpy import hstack as np_hstack
12
  from numpy import array as np_array
13
- from numpy import apply_along_axis as np_apply_along_axis
14
- from numpy import bincount as np_bincount
15
- from numpy import partition as np_partition
16
- from numpy import count_nonzero as np_count_nonzero
17
- from numpy import argsort as np_argsort
18
- from numpy import unique as np_unique
19
- from numpy import vectorize as np_vectorize
20
  from numpy import nan as np_nan
21
  from numpy import inf as np_inf
22
  from numpy import zeros as np_zeros
@@ -32,7 +20,6 @@ from pandas import errors as pd_errors
32
  from pandas import merge, to_numeric
33
  from pandas import options as poptions
34
  from pandas import set_option
35
- from pandas import notnull
36
 
37
  # Time
38
  import time
@@ -48,24 +35,20 @@ import os
48
  import subprocess
49
  from ortools.linear_solver import pywraplp
50
  from random import random
51
- from random import randint
52
  from random import choice
53
 
54
  # Ownership Models
55
- # from NHL_own_regress import xgb_model, lgb_model, knn_model
56
 
57
  pd_options.mode.chained_assignment = None # default='warn'
58
  from warnings import simplefilter
59
  simplefilter(action="ignore", category=pd_errors.PerformanceWarning)
60
  poptions.mode.chained_assignment = None # default='warn'
61
  set_option('future.no_silent_downcasting', True)
62
- from gspread.auth import service_account_from_dict
63
- from discordwebhook import Discord
64
- from pymongo import MongoClient, UpdateOne
65
 
66
  import streamlit as st
67
 
68
- from database import *
69
 
70
  nan_value = float("NaN")
71
  sim_teams = []
@@ -731,12 +714,19 @@ def build_dk_player_level_basic_outcomes(slate_info, dk_player_hold, fd_player_h
731
  feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
732
  X_current = basic_own_df[feature_cols]
733
 
734
- # Use simplified ownership prediction (faster than ML models)
735
- # Base prediction on value and salary
 
 
 
 
 
 
736
  basic_own_df['Combo'] = (
737
- (basic_own_df['value'] * 10) *
738
- (100 / (basic_own_df['Salary'] / 1000))
739
- ) / 100
 
740
 
741
  basic_own_df['Combo'] = np_where((basic_own_df['value'] < 1.5) & (basic_own_df['Salary'] < 7500), basic_own_df['Combo'] * .75, basic_own_df['Combo'])
742
  basic_own_df['Combo'] = np_where((basic_own_df['Salary'] > 5000) & (basic_own_df['value'] < 1.5), basic_own_df['value'], basic_own_df['Combo'])
 
1
  import time
 
 
 
2
  import re
3
 
4
  # Numpy
5
  from numpy import where as np_where
 
6
  from numpy import random as np_random
 
7
  from numpy import array as np_array
 
 
 
 
 
 
 
8
  from numpy import nan as np_nan
9
  from numpy import inf as np_inf
10
  from numpy import zeros as np_zeros
 
20
  from pandas import merge, to_numeric
21
  from pandas import options as poptions
22
  from pandas import set_option
 
23
 
24
  # Time
25
  import time
 
35
  import subprocess
36
  from ortools.linear_solver import pywraplp
37
  from random import random
 
38
  from random import choice
39
 
40
  # Ownership Models
41
+ from func.NHL_own_regress import xgb_model, lgb_model, knn_model
42
 
43
  pd_options.mode.chained_assignment = None # default='warn'
44
  from warnings import simplefilter
45
  simplefilter(action="ignore", category=pd_errors.PerformanceWarning)
46
  poptions.mode.chained_assignment = None # default='warn'
47
  set_option('future.no_silent_downcasting', True)
 
 
 
48
 
49
  import streamlit as st
50
 
51
+ from src.database import *
52
 
53
  nan_value = float("NaN")
54
  sim_teams = []
 
714
  feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
715
  X_current = basic_own_df[feature_cols]
716
 
717
+ print(X_current)
718
+
719
+ # Make predictions with all your models
720
+ basic_own_df['XGB'] = np_clip(xgb_model.predict(X_current), 0, 100)
721
+ basic_own_df['LGB'] = np_clip(lgb_model.predict(X_current), 0, 100) * 100
722
+ basic_own_df['KNN'] = np_clip(knn_model.predict(X_current), 0, 100)
723
+
724
+ # Create combo prediction
725
  basic_own_df['Combo'] = (
726
+ (basic_own_df['XGB'] * .30) +
727
+ (basic_own_df['LGB'] * .30) +
728
+ (basic_own_df['KNN'] * .40)
729
+ )
730
 
731
  basic_own_df['Combo'] = np_where((basic_own_df['value'] < 1.5) & (basic_own_df['Salary'] < 7500), basic_own_df['Combo'] * .75, basic_own_df['Combo'])
732
  basic_own_df['Combo'] = np_where((basic_own_df['Salary'] > 5000) & (basic_own_df['value'] < 1.5), basic_own_df['value'], basic_own_df['Combo'])
src/streamlit_app.py CHANGED
@@ -17,7 +17,8 @@ from pandas import set_option
17
  # Time
18
  import time
19
  from time import sleep as time_sleep
20
- from datetime import datetime
 
21
 
22
  from database import *
23
  from sports.nhl_functions import *
@@ -237,8 +238,252 @@ if selected_tab == "NFL Updates":
237
  st.write("NFL functionality will be added later on.")
238
 
239
  if selected_tab == "NBA Updates":
240
- st.info("NBA updates coming soon!")
241
- st.write("NBA functionality will be added later on.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
  if selected_tab == "MLB Updates":
244
  st.info("MLB updates coming soon!")
 
17
  # Time
18
  import time
19
  from time import sleep as time_sleep
20
+ from datetime import datetime, date
21
+ from pytz import timezone as pytz_timezone
22
 
23
  from database import *
24
  from sports.nhl_functions import *
 
238
  st.write("NFL functionality will be added later on.")
239
 
240
  if selected_tab == "NBA Updates":
241
+
242
+ if st.button(f"🏀 Update NBA models and generate seed frames", type="primary", use_container_width=True):
243
+ x: int = 1
244
+ high_end: int = 1
245
+
246
+ while x <= high_end:
247
+ try:
248
+ try:
249
+ sh = gc.open_by_url(NBA_Master_hold)
250
+ worksheet = sh.worksheet('Game_Adj')
251
+ except:
252
+ sh = gc2.open_by_url(NBA_Master_hold)
253
+ worksheet = sh.worksheet('Game_Adj')
254
+
255
+ t_range = worksheet.range('T2:T32')
256
+ # Create a list of zeros with the same length as t_range
257
+ values = [0] * len(t_range)
258
+ worksheet.update('T2:T32', [[0] for _ in range(len(t_range))])
259
+
260
+ # Sleep for 2 seconds
261
+ time.sleep(2)
262
+
263
+ # Get values from Z2:Z32
264
+ z_values = worksheet.range('Z2:Z32')
265
+
266
+ z_data = [cell.value for cell in z_values]
267
+ worksheet.update([[val] for val in z_data], 'T2:T32')
268
+ except:
269
+ pass
270
+
271
+ ROO_creation_var = 0
272
+ SD_ROO_creation_var = 0
273
+ DK_SD_seed_frame_var = 0
274
+ FD_SD_seed_frame_var = 0
275
+ DK_seed_frame_var = 0
276
+ FD_seed_frame_var = 0
277
+ upload_mongo_dfs_var = 0
278
+ upload_mongo_bets_var = 0
279
+
280
+ if ROO_creation_var == 0:
281
+ try:
282
+ DK_ROO = DK_NBA_ROO_Build(dk_roo_player_hold)
283
+ FD_ROO = FD_NBA_ROO_Build(fd_roo_player_hold)
284
+
285
+ solver_DK = DK_ROO.copy()
286
+ solver_FD = FD_ROO.copy()
287
+ solver_DK = solver_DK.drop_duplicates(subset=['Player'])
288
+ solver_DK['Player'] = solver_DK['Player'].replace('Ron Holland', 'Ronald Holland II')
289
+ solver_FD = solver_FD.drop_duplicates(subset=['Player'])
290
+ solver_FD['Player'] = solver_FD['Player'].replace('Ron Holland', 'Ronald Holland II')
291
+
292
+ try:
293
+ solver_DK['Timestamp'] = str(date.today())
294
+ sh = gc.open_by_url('https://docs.google.com/spreadsheets/d/1H7kdaxVF7Bv3kb1DSa_3Dq6OaC9ajq9UAQfVyDluXzk/edit?gid=0#gid=0')
295
+ worksheet = sh.worksheet('NBA DK')
296
+ worksheet.batch_clear(['A:AB'])
297
+ worksheet.update([solver_DK.columns.values.tolist()] + solver_DK.values.tolist())
298
+ except:
299
+ sh = gc2.open_by_url(NBA_Master_hold)
300
+ worksheet = sh.worksheet('NBA DK')
301
+ worksheet.batch_clear(['A:AB'])
302
+ worksheet.update([solver_DK.columns.values.tolist()] + solver_DK.values.tolist())
303
+
304
+ try:
305
+ solver_FD['Timestamp'] = str(date.today())
306
+ sh = gc.open_by_url('https://docs.google.com/spreadsheets/d/1H7kdaxVF7Bv3kb1DSa_3Dq6OaC9ajq9UAQfVyDluXzk/edit?gid=0#gid=0')
307
+ worksheet = sh.worksheet('NBA FD')
308
+ worksheet.batch_clear(['A:AB'])
309
+ worksheet.update([solver_FD.columns.values.tolist()] + solver_FD.values.tolist())
310
+ except:
311
+ sh = gc2.open_by_url(NBA_Master_hold)
312
+ worksheet = sh.worksheet('NBA FD')
313
+ worksheet.batch_clear(['A:AB'])
314
+ worksheet.update([solver_FD.columns.values.tolist()] + solver_FD.values.tolist())
315
+
316
+ time.sleep(3)
317
+
318
+ roo_final = pd_concat([DK_ROO, FD_ROO])
319
+ roo_final['Salary'] = roo_final['Salary'].astype(int)
320
+
321
+ tz = pytz_timezone('US/Central')
322
+ central_tz = datetime.now(tz)
323
+ current_time = central_tz.strftime("%H:%M:%S")
324
+
325
+ roo_final['timestamp'] = current_time
326
+
327
+ try:
328
+ sh = gc.open_by_url(NBA_Master_hold)
329
+ worksheet = sh.worksheet('Player_Level_ROO')
330
+ worksheet.batch_clear(['A:AB'])
331
+ worksheet.update([roo_final.columns.values.tolist()] + roo_final.values.tolist())
332
+ except:
333
+ sh = gc2.open_by_url(NBA_Master_hold)
334
+ worksheet = sh.worksheet('Player_Level_ROO')
335
+ worksheet.batch_clear(['A:AB'])
336
+ worksheet.update([roo_final.columns.values.tolist()] + roo_final.values.tolist())
337
+
338
+ ROO_creation_var = 1
339
+ except:
340
+ pass
341
+
342
+ if ROO_creation_var == 1:
343
+ try:
344
+ discord.post(content="NBA ROO structure refreshed")
345
+ except:
346
+ pass
347
+ else:
348
+ try:
349
+ discord.post(content="NBA ROO structure script broke")
350
+ except:
351
+ pass
352
+
353
+ time_sleep(1)
354
+
355
+ upload_dfs_data(client, roo_final)
356
+ discord.post(content="NBA DFS database refreshed")
357
+
358
+ upload_betting_data(client)
359
+ discord.post(content="NBA betting database refreshed")
360
+
361
+ try:
362
+ trending_script()
363
+ discord.post(content="NBA Trending Tables refreshed")
364
+ except:
365
+ discord.post(content="NBA Trending Tables broke")
366
+ pass
367
+
368
+ if DK_seed_frame_var == 0:
369
+ DK_NBA_seed_frame(roo_final, client)
370
+ DK_seed_frame_var = 1
371
+
372
+ if DK_seed_frame_var == 1:
373
+ try:
374
+ discord.post(content="NBA Draftkings Seed Frames refreshed")
375
+ except:
376
+ pass
377
+ else:
378
+ try:
379
+ discord.post(content="NBA Draftkings Seed Frames script broke")
380
+ except:
381
+ pass
382
+
383
+ time_sleep(1)
384
+
385
+ if FD_seed_frame_var == 0:
386
+ FD_NBA_seed_frame(roo_final, client)
387
+ FD_seed_frame_var = 1
388
+
389
+ if FD_seed_frame_var == 1:
390
+ try:
391
+ discord.post(content="NBA Fanduel Seed Frames refreshed")
392
+ except:
393
+ pass
394
+ else:
395
+ try:
396
+ discord.post(content="NBA Fanduel Seed Frames script broke")
397
+ except:
398
+ pass
399
+
400
+ time_sleep(1)
401
+
402
+ if SD_ROO_creation_var == 0:
403
+ DK_SD_ROO = DK_SD_NBA_ROO_Build(dk_sd_player_hold, dk_showdown_options, dk_sd_projections)
404
+ FD_SD_ROO = FD_SD_NBA_ROO_Build(fd_sd_player_hold, fd_showdown_options, fd_sd_projections)
405
+
406
+ sd_roo_final = pd_concat([DK_SD_ROO, FD_SD_ROO])
407
+ sd_roo_final['Salary'] = sd_roo_final['Salary'].astype(int)
408
+
409
+ tz = pytz_timezone('US/Central')
410
+ central_tz = datetime.now(tz)
411
+ current_time = central_tz.strftime("%H:%M:%S")
412
+
413
+ sd_roo_final['timestamp'] = current_time
414
+
415
+ try:
416
+ sh = gc.open_by_url(NBA_Master_hold)
417
+ worksheet = sh.worksheet('Player_Level_SD_ROO')
418
+ worksheet.batch_clear(['A:AB'])
419
+ worksheet.update([sd_roo_final.columns.values.tolist()] + sd_roo_final.values.tolist())
420
+ except:
421
+ sh = gc2.open_by_url(NBA_Master_hold)
422
+ worksheet = sh.worksheet('Player_Level_SD_ROO')
423
+ worksheet.batch_clear(['A:AB'])
424
+ worksheet.update([sd_roo_final.columns.values.tolist()] + sd_roo_final.values.tolist())
425
+
426
+ ROO_creation_var = 1
427
+
428
+ if ROO_creation_var == 1:
429
+ try:
430
+ discord.post(content="NBA SD ROO structure refreshed")
431
+ except:
432
+ pass
433
+ else:
434
+ try:
435
+ discord.post(content="NBA SD ROO structure script broke")
436
+ except:
437
+ pass
438
+
439
+ time_sleep(1)
440
+
441
+ if DK_SD_seed_frame_var == 0:
442
+ DK_NBA_SD_seed_frame(dk_showdown_options, dk_sd_projections)
443
+ DK_SD_seed_frame_var = 1
444
+
445
+ if DK_SD_seed_frame_var == 1:
446
+ try:
447
+ discord.post(content="NBA Draftkings SD Seed Frames refreshed")
448
+ except:
449
+ pass
450
+ else:
451
+ try:
452
+ discord.post(content="NBA Draftkings SD Seed Frames script broke")
453
+ except:
454
+ pass
455
+
456
+ time_sleep(1)
457
+
458
+ if FD_SD_seed_frame_var == 0:
459
+ FD_NBA_SD_seed_frame(fd_showdown_options, fd_sd_projections)
460
+ FD_SD_seed_frame_var = 1
461
+
462
+ if FD_SD_seed_frame_var == 1:
463
+ try:
464
+ discord.post(content="NBA Fanduel SD Seed Frames refreshed")
465
+ except:
466
+ pass
467
+ else:
468
+ try:
469
+ discord.post(content="NBA Fanduel SD Seed Frames script broke")
470
+ except:
471
+ pass
472
+
473
+ time_sleep(1)
474
+
475
+ upload_sd_dfs_data(client, sd_roo_final)
476
+ discord.post(content="NBA SD DFS database refreshed")
477
+
478
+ print (f'currently on run {x}, {high_end - x} runs remaining')
479
+
480
+ x += 1
481
+
482
+ if high_end > 1:
483
+ time_sleep(600)
484
+
485
+ st.success("✅ NBA updates completed successfully!")
486
+ st.balloons()
487
 
488
  if selected_tab == "MLB Updates":
489
  st.info("MLB updates coming soon!")