diff --git a/.gitignore b/.gitignore index 263fa97..9aae150 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /nhle_scoreboard_response.txt /nhle_standings_response.txt /nhl_standings.db -/__pycache__ \ No newline at end of file +/scoreboard_data.json +/__pycache__ +/app/__pycache__ +/app/scoreboard/__pycache__ diff --git a/app.py b/app.py deleted file mode 100644 index 9aa4a30..0000000 --- a/app.py +++ /dev/null @@ -1,254 +0,0 @@ -from flask import Flask, render_template, jsonify -import requests -from datetime import datetime, timedelta -from waitress import serve -import sqlite3 -import threading -import time -import schedule -import json -from update_nhl_standings_db import update_nhl_standings - - -app = Flask(__name__) - -# Configuration -scoreboard_data = None - -# Data Retrieval -def get_nhle_scoreboard(): - now = datetime.now() - start_time_evening = now.replace(hour=23, minute=0, second=0, microsecond=0) # 7:00 PM EST - end_time_evening = now.replace(hour=8, minute=0, second=0, microsecond=0) # 3:00 AM EST - - if now >= start_time_evening or now < end_time_evening: - # Use now URL - nhle_api_url = "https://api-web.nhle.com/v1/score/now" - - else: - # Use current data URL - nhle_api_url = f"https://api-web.nhle.com/v1/score/{now.strftime('%Y-%m-%d')}" - - response = requests.get(nhle_api_url) - if response.status_code == 200: - return response.json() - else: - print("Error:", response.status_code) - -# Store scoreboard data locally -def store_scoreboard_data(): - global scoreboard_data - scoreboard_data = get_nhle_scoreboard() - -# Schedule tasks -def schedule_tasks(): - schedule.every(300).seconds.do(update_nhl_standings) - schedule.every(10).seconds.do(store_scoreboard_data) - while True: - schedule.run_pending() - time.sleep(1) - -# Data Processing -def extract_game_info(): - global scoreboard_data - if scoreboard_data: - extracted_info = [] - for game in scoreboard_data.get("games", []): - home_team = game["homeTeam"]["name"]["default"] - away_team = game["awayTeam"]["name"]["default"] - home_logo = game["homeTeam"]["logo"] - away_logo = game["awayTeam"]["logo"] - game_state = convert_game_state(game["gameState"]) - period, time_remaining, time_running, is_intermission = process_time_and_period(game_state, game) - home_score, away_score, home_shots, away_shots = process_scores_and_shots(game_state, game) - start_time_str, home_record, away_record = process_start_time_and_records(game_state, game) - game_priority = calculate_game_priority(game) - - # Get power play information - home_power_play = get_power_play_info(game, home_team) - away_power_play = get_power_play_info(game, away_team) - - # Get game outcome - last_period_type = get_game_outcome(game_state, game) - - - extracted_info.append({ - "Home Team": home_team, - "Home Score": home_score, - "Away Team": away_team, - "Away Score": away_score, - "Home Logo": home_logo, - "Away Logo": away_logo, - "Game State": game_state, - "Period": period, - "Time Remaining": time_remaining, - "Time Running": time_running, - "Intermission": is_intermission, - "Priority": game_priority, - "Start Time": start_time_str, - "Home Record": home_record, - "Away Record": away_record, - "Home Shots": home_shots, - "Away Shots": away_shots, - "Home Power Play": home_power_play, - "Away Power Play": away_power_play, - "Last Period Type": last_period_type - }) - - # Sort games based on priority - sorted_info = sorted(extracted_info, key=lambda x: x["Priority"], reverse=True) - return sorted_info - -def convert_game_state(game_state): - if game_state == "OFF": - return "FINAL" - elif game_state == "CRIT": - return "LIVE" - elif game_state == "FUT": - return "PRE" - else: - return game_state - -def process_time_and_period(game_state, game): - if game_state in ["PRE", "FUT"]: - return 0, "20:00", False, False - elif game_state in ["FINAL", "OFF"]: - return "N/A", "00:00", False, False - else: - period = game["periodDescriptor"]["number"] - time_remaining = game["clock"]["timeRemaining"] - if time_remaining == "00:00": - time_remaining = "END" - time_running = game["clock"]["running"] - is_intermission = game["clock"]["inIntermission"] - return period, time_remaining, time_running, is_intermission - -def process_scores_and_shots(game_state, game): - if game_state in ["PRE", "FUT"]: - return 0, 0, 0, 0 - else: - return game["homeTeam"]["score"], game["awayTeam"]["score"], game["homeTeam"]["sog"], game["awayTeam"]["sog"] - -def process_start_time_and_records(game_state, game): - if game_state in ["PRE", "FUT"]: - start_time_utc = game["startTimeUTC"] - start_time_str = utc_to_est_time(start_time_utc) - home_record = game["homeTeam"]["record"] - away_record = game["awayTeam"]["record"] - game_state = "PRE" - else: - start_time_str = "N/A" - home_record = "N/A" - away_record = "N/A" - return start_time_str, home_record, away_record - -def get_power_play_info(game, team_name): - if "situation" in game and "situationDescriptions" in game["situation"]: - for situation in game["situation"]["situationDescriptions"]: - if situation == "PP" and game["awayTeam"]["name"]["default"] == team_name: - return f"PP {game['situation']['timeRemaining']}" - elif situation == "PP" and game["homeTeam"]["name"]["default"] == team_name: - return f"PP {game['situation']['timeRemaining']}" - return "" # Return empty string if team is not on power play - -def get_game_outcome(game_state, game): - if game_state == "FINAL": - last_period_type = game["gameOutcome"]["lastPeriodType"] - else: - last_period_type = "N/A" - - return last_period_type - -def calculate_game_priority(game): - if game["gameState"] in ["FINAL", "OFF", "PRE", "FUT"] or game["clock"]["inIntermission"]: - return 0 - else: - # Get standings information from the database for home and away teams - home_team_standings = get_team_standings(game["homeTeam"]["name"]["default"]) - away_team_standings = get_team_standings(game["awayTeam"]["name"]["default"]) - - # Calculate total values of leagueSequence + leagueL10Sequence for each team - home_team_total = home_team_standings["league_sequence"] + home_team_standings["league_l10_sequence"] - away_team_total = away_team_standings["league_sequence"] + away_team_standings["league_l10_sequence"] - - # Calculate the priority adjustment factor by subtracting away team's total from home team's total - matchup_adjustment = home_team_total + away_team_total - - period = game.get("periodDescriptor", {}).get("number", 0) - time_remaining = game.get("clock", {}).get("secondsRemaining", 0) - home_score = game["homeTeam"]["score"] - away_score = game["awayTeam"]["score"] - score_difference = abs(home_score - away_score) - score_total = (home_score + away_score) * 20 - - if period == 4: - base_priority = 400 - elif period == 3: - base_priority = 300 - elif period == 2: - base_priority = 200 - else: - base_priority = 100 - - if score_difference > 3: - base_priority -= 500 - elif score_difference > 2: - base_priority -= 350 - elif score_difference > 1: - base_priority -= 100 - - if score_difference == 0 and period == 3 and time_remaining <= 600: - base_priority += 100 - - time_priority = (1200 - time_remaining) / 20 - - # Add the priority adjustment factor to the base priority - return int(base_priority + time_priority - matchup_adjustment + score_total) - -def get_team_standings(team_name): - conn = sqlite3.connect("nhl_standings.db") - cursor = conn.cursor() - cursor.execute(""" - SELECT league_sequence, league_l10_sequence - FROM standings - WHERE team_common_name = ? - """, (team_name,)) - result = cursor.fetchone() - conn.close() - if result: - return {"league_sequence": result[0], "league_l10_sequence": result[1]} - else: - return {"league_sequence": 0, "league_l10_sequence": 0} - -def utc_to_est_time(utc_time): - utc_datetime = datetime.strptime(utc_time, "%Y-%m-%dT%H:%M:%SZ") - est_offset = timedelta(hours=-5) - est_datetime = utc_datetime + est_offset - est_time_str = est_datetime.strftime("%I:%M %p") - return est_time_str - -# Routes -@app.route('/') -def index(): - return render_template('index.html') - -@app.route('/scoreboard') -def get_scoreboard(): - global scoreboard_data - if scoreboard_data: - live_games = [game for game in extract_game_info() if game["Game State"] == "LIVE"] - pre_games = [game for game in extract_game_info() if game["Game State"] == "PRE"] - final_games = [game for game in extract_game_info() if game["Game State"] == "FINAL"] - return jsonify({ - "live_games": live_games, - "pre_games": pre_games, - "final_games": final_games - }) - else: - return jsonify({"error": "Failed to retrieve scoreboard data"}) - -if __name__ == '__main__': - store_scoreboard_data() - update_nhl_standings() - threading.Thread(target=schedule_tasks).start() - serve(app, host="0.0.0.0", port=2897) \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..96c8ef5 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,5 @@ +from flask import Flask + +app = Flask(__name__) + +from app import routes diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..aff021e --- /dev/null +++ b/app/routes.py @@ -0,0 +1,32 @@ +from app import app +from flask import render_template, jsonify +from app.scoreboard.process_data import extract_game_info +import json + +SCOREBOARD_DATA_FILE = 'scoreboard_data.json' + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/scoreboard') +def get_scoreboard(): + try: + with open(SCOREBOARD_DATA_FILE, 'r') as json_file: + scoreboard_data = json.load(json_file) + except FileNotFoundError: + return jsonify({"error": "Failed to retrieve scoreboard data. File not found."}) + except json.JSONDecodeError: + return jsonify({"error": "Failed to retrieve scoreboard data. Invalid JSON format."}) + + if scoreboard_data: + live_games = [game for game in extract_game_info(scoreboard_data) if game["Game State"] == "LIVE"] + pre_games = [game for game in extract_game_info(scoreboard_data) if game["Game State"] == "PRE"] + final_games = [game for game in extract_game_info(scoreboard_data) if game["Game State"] == "FINAL"] + return jsonify({ + "live_games": live_games, + "pre_games": pre_games, + "final_games": final_games + }) + else: + return jsonify({"error": "Failed to retrieve scoreboard data"}) diff --git a/app/scoreboard/__init__.py b/app/scoreboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/scoreboard/get_data.py b/app/scoreboard/get_data.py new file mode 100644 index 0000000..f7f5c6a --- /dev/null +++ b/app/scoreboard/get_data.py @@ -0,0 +1,34 @@ +import requests +from datetime import datetime +import json + +SCOREBOARD_DATA_FILE = 'scoreboard_data.json' + +def get_scoreboard_data(): + now = datetime.now() + start_time_evening = now.replace(hour=23, minute=0, second=0, microsecond=0) # 7:00 PM EST + end_time_evening = now.replace(hour=8, minute=0, second=0, microsecond=0) # 3:00 AM EST + + if now >= start_time_evening or now < end_time_evening: + # Use now URL + nhle_api_url = "https://api-web.nhle.com/v1/score/now" + + else: + # Use current data URL + nhle_api_url = f"https://api-web.nhle.com/v1/score/{now.strftime('%Y-%m-%d')}" + + response = requests.get(nhle_api_url) + if response.status_code == 200: + return response.json() + else: + print("Error:", response.status_code) + +# Store scoreboard data locally +def store_scoreboard_data(): + scoreboard_data = get_scoreboard_data() + if scoreboard_data: + with open(SCOREBOARD_DATA_FILE, 'w') as json_file: + json.dump(scoreboard_data, json_file) + return scoreboard_data + else: + return None \ No newline at end of file diff --git a/app/scoreboard/process_data.py b/app/scoreboard/process_data.py new file mode 100644 index 0000000..8d11926 --- /dev/null +++ b/app/scoreboard/process_data.py @@ -0,0 +1,139 @@ +import sqlite3 +from datetime import datetime, timedelta + +def extract_game_info(scoreboard_data): + if not scoreboard_data: + return [] + + extracted_info = [] + for game in scoreboard_data.get("games", []): + extracted_info.append({ + "Home Team": game["homeTeam"]["name"]["default"], + "Home Score": game["homeTeam"]["score"], + "Away Team": game["awayTeam"]["name"]["default"], + "Away Score": game["awayTeam"]["score"], + "Home Logo": game["homeTeam"]["logo"], + "Away Logo": game["awayTeam"]["logo"], + "Game State": convert_game_state(game["gameState"]), + "Period": process_period(game), + "Time Remaining": process_time_remaining(game), + "Time Running": game["clock"]["running"], + "Intermission": game["clock"]["inIntermission"], + "Priority": calculate_game_priority(game), + "Start Time": process_start_time(game), + "Home Record": game["homeTeam"]["record"] if game["gameState"] in ["PRE", "FUT"] else "N/A", + "Away Record": game["awayTeam"]["record"] if game["gameState"] in ["PRE", "FUT"] else "N/A", + "Home Shots": game["homeTeam"]["sog"] if game["gameState"] not in ["PRE", "FUT"] else 0, + "Away Shots": game["awayTeam"]["sog"] if game["gameState"] not in ["PRE", "FUT"] else 0, + "Home Power Play": get_power_play_info(game, game["homeTeam"]["name"]["default"]), + "Away Power Play": get_power_play_info(game, game["awayTeam"]["name"]["default"]), + "Last Period Type": get_game_outcome(game) + }) + + # Sort games based on priority + return sorted(extracted_info, key=lambda x: x["Priority"], reverse=True) + +def convert_game_state(game_state): + state_mapping = {"OFF": "FINAL", "CRIT": "LIVE", "FUT": "PRE"} + return state_mapping.get(game_state, game_state) + +def process_period(game): + if game["gameState"] in ["PRE", "FUT"]: + return 0 + elif game["gameState"] in ["FINAL", "OFF"]: + return "N/A" + else: + return game["periodDescriptor"]["number"] + +def process_time_remaining(game): + if game["gameState"] in ["PRE", "FUT"]: + return "20:00" + elif game["gameState"] in ["FINAL", "OFF"]: + return "00:00" + else: + time_remaining = game["clock"]["timeRemaining"] + return "END" if time_remaining == "00:00" else time_remaining + +def process_start_time(game): + if game["gameState"] in ["PRE", "FUT"]: + utc_time = game["startTimeUTC"] + return utc_to_est_time(utc_time) + else: + return "N/A" + +def get_power_play_info(game, team_name): + if "situation" in game and "situationDescriptions" in game["situation"]: + for situation in game["situation"]["situationDescriptions"]: + if situation == "PP" and game["awayTeam"]["name"]["default"] == team_name: + return f"PP {game['situation']['timeRemaining']}" + elif situation == "PP" and game["homeTeam"]["name"]["default"] == team_name: + return f"PP {game['situation']['timeRemaining']}" + return "" + +def get_game_outcome(game): + return game["gameOutcome"]["lastPeriodType"] if game["gameState"] == "FINAL" else "N/A" + +def calculate_game_priority(game): + # Return 0 if game is in certain states + if game["gameState"] in ["FINAL", "OFF", "PRE", "FUT"] or game["clock"]["inIntermission"]: + return 0 + + # Get standings for home and away teams + home_team_standings = get_team_standings(game["homeTeam"]["name"]["default"]) + away_team_standings = get_team_standings(game["awayTeam"]["name"]["default"]) + + # Calculate total values of leagueSequence + leagueL10Sequence for each team + home_total = home_team_standings["league_sequence"] + home_team_standings["league_l10_sequence"] + away_total = away_team_standings["league_sequence"] + away_team_standings["league_l10_sequence"] + + # Calculate the matchup adjustment factor + matchup_adjustment = home_total + away_total + + # Get period, time remaining, scores, and other relevant data + period = game.get("periodDescriptor", {}).get("number", 0) + time_remaining = game.get("clock", {}).get("secondsRemaining", 0) + home_score = game["homeTeam"]["score"] + away_score = game["awayTeam"]["score"] + score_difference = abs(home_score - away_score) + score_total = (home_score + away_score) * 20 + + # Calculate the base priority based on period + base_priority = {4: 400, 3: 300, 2: 200}.get(period, 100) + + # Adjust base priority based on score difference + if score_difference > 3: + base_priority -= 500 + elif score_difference > 2: + base_priority -= 350 + elif score_difference > 1: + base_priority -= 100 + + # Adjust base priority based on certain conditions + if score_difference == 0 and period == 3 and time_remaining <= 600: + base_priority += 100 + + # Calculate time priority + time_priority = (1200 - time_remaining) / 20 + + # Calculate the final priority + final_priority = int(base_priority + time_priority - matchup_adjustment + score_total) + + return final_priority + +def get_team_standings(team_name): + conn = sqlite3.connect("nhl_standings.db") + cursor = conn.cursor() + cursor.execute(""" + SELECT league_sequence, league_l10_sequence + FROM standings + WHERE team_common_name = ? + """, (team_name,)) + result = cursor.fetchone() + conn.close() + return {"league_sequence": result[0] if result else 0, "league_l10_sequence": result[1] if result else 0} + +def utc_to_est_time(utc_time): + utc_datetime = datetime.strptime(utc_time, "%Y-%m-%dT%H:%M:%SZ") + est_offset = timedelta(hours=-5) + est_datetime = utc_datetime + est_offset + return est_datetime.strftime("%I:%M %p") diff --git a/app/scoreboard/tasks.py b/app/scoreboard/tasks.py new file mode 100644 index 0000000..4fae985 --- /dev/null +++ b/app/scoreboard/tasks.py @@ -0,0 +1,13 @@ +import schedule +import time +from app.scoreboard.update_nhl_standings_db import update_nhl_standings +from app.scoreboard.get_data import store_scoreboard_data + +def schedule_tasks(): + schedule.every(600).seconds.do(update_nhl_standings) + schedule.every(10).seconds.do(store_scoreboard_data) + while True: + schedule.run_pending() + time.sleep(1) + + diff --git a/update_nhl_standings_db.py b/app/scoreboard/update_nhl_standings_db.py similarity index 100% rename from update_nhl_standings_db.py rename to app/scoreboard/update_nhl_standings_db.py diff --git a/static/script.js b/app/static/script.js similarity index 100% rename from static/script.js rename to app/static/script.js diff --git a/static/styles.css b/app/static/styles.css similarity index 100% rename from static/styles.css rename to app/static/styles.css diff --git a/templates/index.html b/app/templates/index.html similarity index 100% rename from templates/index.html rename to app/templates/index.html diff --git a/run.py b/run.py new file mode 100644 index 0000000..5cc4ddf --- /dev/null +++ b/run.py @@ -0,0 +1,13 @@ +from app import app +from waitress import serve +import threading +from app.scoreboard.tasks import schedule_tasks +from app.scoreboard.get_data import store_scoreboard_data +from app.scoreboard.update_nhl_standings_db import update_nhl_standings + +if __name__ == '__main__': + store_scoreboard_data() + update_nhl_standings() + threading.Thread(target=schedule_tasks).start() + serve(app, host="0.0.0.0", port=2897) +