From 735e41de84217c126ad3c93c9dbaa5a7e616658b Mon Sep 17 00:00:00 2001 From: Eduardo Figueroa Date: Tue, 9 Sep 2025 16:14:53 -0700 Subject: [PATCH] debug mode added. Fixed gitignore. --- .gitignore | 4 +- app.py | 52 +++++++++++++++++- services/__pycache__/__init__.cpython-313.pyc | Bin 144 -> 0 bytes services/__pycache__/espn_api.cpython-313.pyc | Bin 6985 -> 0 bytes .../__pycache__/sleeper_api.cpython-313.pyc | Bin 4623 -> 0 bytes services/espn_api.py | 29 +++++++--- static/style.css | 26 +++++++++ templates/dashboard.html | 43 +++++++++++++-- 8 files changed, 138 insertions(+), 16 deletions(-) delete mode 100644 services/__pycache__/__init__.cpython-313.pyc delete mode 100644 services/__pycache__/espn_api.cpython-313.pyc delete mode 100644 services/__pycache__/sleeper_api.cpython-313.pyc diff --git a/.gitignore b/.gitignore index d3168d3..7b29c09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Python __pycache__/ -*.py[cod] +*.py[cod]* *$py.class *.so .Python @@ -198,4 +198,4 @@ Thumbs.db .env.development .env.test .env.production -config.local.py \ No newline at end of file +config.local.py diff --git a/app.py b/app.py index 9069e13..e3f253b 100644 --- a/app.py +++ b/app.py @@ -24,9 +24,19 @@ espn_api = ESPNAPI() api_cache = {} CACHE_DURATION = 300 # 5 minutes in seconds +# Debug time override +debug_time_override = None + +def get_debug_time(): + """Get the current time, or debug override time if set""" + global debug_time_override + if debug_time_override: + return debug_time_override + return datetime.now() + def get_cached_or_fetch(cache_key, fetch_function, *args): """Get data from cache or fetch it and cache the result""" - now = datetime.now() + now = get_debug_time() if cache_key in api_cache: cached_data, cached_time = api_cache[cache_key] @@ -56,10 +66,14 @@ def get_league_color(league_index): @app.context_processor def inject_apis(): """Make API and version available to all templates""" + global debug_time_override + is_debug_mode = request.args.get('debug') == 'true' return dict( sleeper_api=sleeper_api, app_version=app.config['VERSION'], - current_theme=session.get('theme', 'dark') + current_theme=session.get('theme', 'dark'), + is_debug_mode=is_debug_mode, + debug_time_override=debug_time_override ) @app.route('/') @@ -102,6 +116,38 @@ def set_timezone(): return_url = request.form.get('return_url', url_for('index')) return redirect(return_url) +# Add debug time override route +@app.route('/set_debug_time', methods=['POST']) +def set_debug_time(): + """Set debug time override (only in debug mode)""" + global debug_time_override + + # Check if debug mode is enabled + if not request.args.get('debug') == 'true': + return jsonify({'error': 'Debug mode not enabled'}), 403 + + debug_datetime_str = request.form.get('debug_time') + if debug_datetime_str: + try: + # Parse the datetime string from the picker (format: YYYY-MM-DDTHH:MM) + debug_time_override = datetime.strptime(debug_datetime_str, '%Y-%m-%dT%H:%M') + print(f"DEBUG: Set debug time override to: {debug_time_override}", flush=True) + # Clear cache when time changes to get fresh data + api_cache.clear() + print("DEBUG: Cleared API cache due to time override", flush=True) + except ValueError as e: + print(f"DEBUG: Invalid debug time format: {debug_datetime_str}, error: {e}", flush=True) + return jsonify({'error': 'Invalid datetime format'}), 400 + else: + # Clear the override + debug_time_override = None + api_cache.clear() + print("DEBUG: Cleared debug time override", flush=True) + + # Get return URL from form or default to index + return_url = request.form.get('return_url', url_for('index')) + return redirect(return_url) + @app.route('/') def dashboard_current(username): """Dashboard for current NFL week""" @@ -166,7 +212,7 @@ def dashboard(username, week): print(f"DEBUG: Using timezone: {user_timezone}", flush=True) print(f"DEBUG: Calling get_week_schedule() for week {week}, season {season}", flush=True) try: - schedule = espn_api.get_week_schedule(week, season, user_timezone) + schedule = espn_api.get_week_schedule(week, season, user_timezone, debug_time_override) print(f"DEBUG: Schedule retrieved successfully", flush=True) except Exception as e: print(f"ERROR: ESPN schedule fetch failed: {str(e)}", flush=True) diff --git a/services/__pycache__/__init__.cpython-313.pyc b/services/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 04319b776215397d32a67f6fa683e5b0fa96eebc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmey&%ge<81e|HRGBkknV-N=h7@>^MEI`IohI9r^M!%H|MNB~6XOPq_ef`|L68++g z#G+Jvm(()-w8XrU#Nx{2qWnDl;?$zD%;eN!{rLFIyv&mLc)fzkTO2mI`6;D2sdh!I UK>Z-|i$RQ!%#4hTMa)1J0Gm4@WdHyG diff --git a/services/__pycache__/espn_api.cpython-313.pyc b/services/__pycache__/espn_api.cpython-313.pyc deleted file mode 100644 index cb8be8cf71cc451cf7de02c125a2adafd084e3db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6985 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 T(Fv&h!F1I?cwQk~s@nb!F?U-~ diff --git a/services/__pycache__/sleeper_api.cpython-313.pyc b/services/__pycache__/sleeper_api.cpython-313.pyc deleted file mode 100644 index 3c7f31636f3deb721efc6e8d7a1274c2f142fe4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4623 zcmd^DO>7&-6`tji;)+^|iWrNEWJybkw#3xdHj!;4ft6TAY&mu!)ZD6dtOA=gwUiiB zOJ-*2RD_KRJ+y2SAaGm3J@nup1!AAtqky7law&QwOBH0dDPW+z$EnQ$?$t?87h z87;G-TM@5VDxxu$z^ZpT))+v5DWsChDNI3x0q?|90~#W+V;B-nGG@uwm^_OQ@UlJT@AEs*=e`77mnQm*Gs1PlP*q% zZ^4(wMRp_)3yFZfyF&>@PoE@lvS=TBY?BrrZJF+Uv@S?AShGaN$g(d>X819ZRyXJiY~)it|dXsi0eE*b0P`FjGz*(`opH!a~Sv&va@A^p15N3|T&)og*P z%+$;hv;1$ffsfGe3HzDklE%StQnoBx;dFBQjl!Oz#k`d+-m@*aIGZ zAMW(SB;T3so)yWb;^^#g{?lU|$p7neGq~@$QFBW)3dWkJPlNPu4kCo8It-P25$^TC zRIjc<3+tISf8MJ)gni!vaRA!E&9&;}YxUTL{}gRH29xaJRP<__6MhJpjVHng)4moP z06QGO2HY33LCRN7AsU$yrMh8C^XFcd&?9we@?BlOc1&Wr299}5(#=e&9FlFFY-IEI z0=8pQG%X!K5koNuBIUDP)IkjS(R0Z#q5^$wpVzIy4!l~LQRu^71hG!;M`O27-#Xoh zj@P2&cg&A(et7dv;TKn`$usrnY(;1aeGMUA6XILKcrz0F;O6&lzW42_FiM|--kElu zMb5Nw_$g$b3=YW53h*)TbD)I?zrY`W2!8+~eEBFMqUG~aUe}i4rj&puCCy`6$T^_c z-ck7e9HUn!a4U^ky}Ar5;`UUqvW^2(4;nW~Eg0;Bm|(X~vR-uJcfEfsw&&yrz5b zvAUfu$X$Ny;&{wnj6}r9>u$w5@bMJS9)!*1&D`eVZ$nk(8wgMG&3g38LD;lMHeE2j z?L9w%%#-wg?oOJuP%Jzvqk!9QN zJY-m;_m1R6xDWyJX^bN}lBaKftz4FN-zxjtEeU~$b3-B@xiD>sgFlK9QkOh z^6Hj2+8jT+B}(@r(MBX*i^Qu!+_|gU*{1c5x&o`T*~WeSK(UoPJna_|TDT)E;wW}n z;5?4;ehoN7fGp?m1uN>ATsD_+p|u|~5Po6+Vuv>t&rIsxsaCgRMda+DXXqZU4qicQ zFMUUlMB3EWPzRB;>F{o4$mBpK{R55uM6Ewj?@v~O&4H2I*KS?=+4!B@M^p8I=PEsb zR^M<{7_w~#Yi*q?`yj!e59i>20C?%*0|{QbAyr*1ES2(@7ghD0l9qQh5mn9RC^Pdp zLpKVl>V$F;-1-QL1d5|5aA6erj!vVnt#bx))*;o7=W-cUGfkSiS~7K}s`rS!Hv26` z5ei$Nao7nE-zCk+m)6g3`}sg}I}{8IZui^59~j#f_lUoW;XvYVqg{bvXFwbac8o7Y x+p(O%=dg&Z;6DCNJNtc^o`*U-Jq*KBc#?74A4%j-WaRJuIgT6NCMfNW{{&R*k}d!M diff --git a/services/espn_api.py b/services/espn_api.py index 4ce9f62..da549ce 100644 --- a/services/espn_api.py +++ b/services/espn_api.py @@ -35,7 +35,7 @@ class ESPNAPI: print(f"ESPN API: Error fetching injury status for {player_name}: {e}", flush=True) return None - def get_week_schedule(self, week, season, user_timezone='America/Los_Angeles'): + def get_week_schedule(self, week, season, user_timezone='America/Los_Angeles', debug_current_time=None): """Get NFL schedule for a specific week""" try: print(f"ESPN API: Fetching schedule for week {week}, season {season}", flush=True) @@ -114,7 +114,7 @@ class ESPNAPI: 'home_team': home_team, 'away_team': away_team, 'teams': [home_team, away_team], - 'is_past': game_date_local < datetime.now(game_date_local.tzinfo), + 'is_past': game_date_local < (debug_current_time.astimezone(game_date_local.tzinfo) if debug_current_time else datetime.now(game_date_local.tzinfo)), 'is_live': is_live, 'game_state': game_state, 'status_display': status_display @@ -125,7 +125,7 @@ class ESPNAPI: print("ESPN API: No 'events' key found in response", flush=True) print(f"ESPN API: Processed {len(games)} games total", flush=True) - schedule = self._organize_by_day(games) + schedule = self._organize_by_day(games, debug_current_time) # Log games by day for day, day_games in schedule.items(): @@ -137,9 +137,10 @@ class ESPNAPI: print(f"ESPN API: Error fetching schedule: {e}", flush=True) return {} - def _organize_by_day(self, games): + def _organize_by_day(self, games, debug_current_time=None): """Organize games by day, sorted chronologically with date info""" schedule = {} + current_time_for_comparison = debug_current_time or datetime.now() # Sort games by date first games_sorted = sorted(games, key=lambda x: x['date_local']) @@ -152,13 +153,27 @@ class ESPNAPI: 'day_name': game['day_of_week'], 'date': game['date_formatted'], 'date_obj': game['date_local'].date(), - 'is_past': game['date_local'].date() < datetime.now(game['date_local'].tzinfo).date(), + 'is_past': game['date_local'].date() < current_time_for_comparison.astimezone(game['date_local'].tzinfo).date(), 'games': [] } schedule[day_key]['games'].append(game) - # Sort games within each day by time + # Sort games within each day: live/future first, then completed, then by time within each group for day_info in schedule.values(): - day_info['games'].sort(key=lambda x: x['date_local']) + def game_sort_key(game): + # Priority: live games first (0), future games (1), completed games (2) + if game['is_live']: + priority = 0 + elif not game['is_past']: + priority = 1 # future games + else: + priority = 2 # completed games + return (priority, game['date_local']) + + day_info['games'].sort(key=game_sort_key) + + # Add day-level status info for day sorting + day_info['has_live_games'] = any(game['is_live'] for game in day_info['games']) + day_info['has_future_games'] = any(not game['is_past'] and not game['is_live'] for game in day_info['games']) return schedule diff --git a/static/style.css b/static/style.css index 5c812b7..79fe901 100644 --- a/static/style.css +++ b/static/style.css @@ -199,6 +199,32 @@ font-size: 1.8rem; } +/* Debug time picker styles */ +.debug-time-picker { + background: var(--bg-secondary); + border: 2px solid #ff6b6b; + border-radius: 8px; + padding: 12px; + margin-top: 12px; + box-shadow: 0 2px 8px rgba(255, 107, 107, 0.2); + width: 100%; +} + +.debug-time-picker input[type="datetime-local"] { + background: var(--bg-color); + color: var(--text-color); + border: 2px solid #ff6b6b; + border-radius: 4px; + padding: 6px 10px; + font-family: inherit; +} + +.debug-time-picker input[type="datetime-local"]:focus { + outline: none; + border-color: #ff4757; + box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2); +} + .week-nav { display: flex; align-items: center; diff --git a/templates/dashboard.html b/templates/dashboard.html index 1b2ec15..d0de849 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -8,16 +8,38 @@

RosterHash for {{ user.display_name }}

-
+
+ + + {% if is_debug_mode %} +
+
+ + + + + +
+ {% if debug_time_override %} +
+ Current override: {{ debug_time_override.strftime('%Y-%m-%d %H:%M') }} +
+ {% endif %} +
+ {% endif %}
@@ -71,8 +93,21 @@ {% set ns = namespace(has_games=false) %} - + + {% set live_future_days = [] %} + {% set completed_days = [] %} {% for day_key, day_info in schedule.items() if day_info.games %} + {% if day_info.has_live_games or day_info.has_future_games %} + {% set _ = live_future_days.append((day_key, day_info)) %} + {% else %} + {% set _ = completed_days.append((day_key, day_info)) %} + {% endif %} + {% endfor %} + {% set sorted_live_future = live_future_days|sort(attribute='1.date_obj') %} + {% set sorted_completed = completed_days|sort(attribute='1.date_obj') %} + + + {% for day_key, day_info in sorted_live_future + sorted_completed %} {% set ns.has_games = true %}