feat: make scoreboard playoff-aware with banner, bracket, and series drill-down
Turn a regular-season-looking Tuesday into a full playoff experience: - Playoff banner with round + day + series + elimination counts, gold/silver Cup theme toggled by body.playoff-mode - Series context on each playoff card: round chip, series score, stake badges (GAME 7, CLINCHER, PIVOTAL), and one-line blurb - Game 7s pin to a new Spotlight section above Live - Playoff OT renders with SUDDEN DEATH badge and pulsing gold border - Client-side OT notifications via bell button in the banner - New /series/<id> drill-down with headline, next-game, and game-by-game history - New /bracket page: 7-column desktop grid, accordion on mobile - Day N banner count auto-anchors on first playoff scoreboard hit - SQLite cache for bracket + per-series schedules, stale-on-failure up to 24h Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+55
-5
@@ -1,10 +1,25 @@
|
||||
import json
|
||||
|
||||
from flask import render_template, jsonify, send_from_directory
|
||||
from flask import abort, render_template, jsonify, send_from_directory
|
||||
|
||||
from app import app
|
||||
from app.config import SCOREBOARD_DATA_FILE
|
||||
from app.games import parse_games
|
||||
from app.playoff import today_meta
|
||||
from app.bracket_view import build_bracket_view
|
||||
from app.playoff_cache import (
|
||||
day_n as compute_day_n,
|
||||
fetch_series,
|
||||
get_bracket,
|
||||
parse_series_id,
|
||||
record_start_date_if_missing,
|
||||
refresh_bracket,
|
||||
)
|
||||
from app.series_view import build_series_view
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
_EASTERN = ZoneInfo("America/New_York")
|
||||
|
||||
|
||||
@app.route("/manifest.json")
|
||||
@@ -45,20 +60,55 @@ def get_scoreboard():
|
||||
)
|
||||
|
||||
if scoreboard_data:
|
||||
raw_games = scoreboard_data.get("games", [])
|
||||
record_start_date_if_missing(raw_games)
|
||||
games = parse_games(scoreboard_data)
|
||||
n, total = compute_day_n()
|
||||
meta = today_meta(raw_games, day_n=n, day_total=total)
|
||||
|
||||
pinned = [g for g in games if g.get("Pinned")]
|
||||
remaining = [g for g in games if not g.get("Pinned")]
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"meta": meta,
|
||||
"pinned_games": pinned,
|
||||
"live_games": [
|
||||
g
|
||||
for g in games
|
||||
for g in remaining
|
||||
if g["Game State"] == "LIVE" and not g["Intermission"]
|
||||
],
|
||||
"intermission_games": [
|
||||
g for g in games if g["Game State"] == "LIVE" and g["Intermission"]
|
||||
g
|
||||
for g in remaining
|
||||
if g["Game State"] == "LIVE" and g["Intermission"]
|
||||
],
|
||||
"pre_games": [g for g in games if g["Game State"] == "PRE"],
|
||||
"final_games": [g for g in games if g["Game State"] == "FINAL"],
|
||||
"pre_games": [g for g in remaining if g["Game State"] == "PRE"],
|
||||
"final_games": [g for g in remaining if g["Game State"] == "FINAL"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
return jsonify({"error": "Failed to retrieve scoreboard data"})
|
||||
|
||||
|
||||
@app.route("/series/<series_id>")
|
||||
def series_detail(series_id):
|
||||
if parse_series_id(series_id) is None:
|
||||
abort(404)
|
||||
payload = fetch_series(series_id)
|
||||
if payload is None:
|
||||
abort(404)
|
||||
view = build_series_view(series_id, payload)
|
||||
return render_template("series.html", series=view)
|
||||
|
||||
|
||||
@app.route("/bracket")
|
||||
def bracket():
|
||||
year = datetime.now(_EASTERN).year
|
||||
payload, fetched_at = get_bracket(year)
|
||||
if payload is None:
|
||||
payload = refresh_bracket(year)
|
||||
if payload is None:
|
||||
abort(404)
|
||||
view = build_bracket_view(year, payload, fetched_at=fetched_at)
|
||||
return render_template("bracket.html", bracket=view)
|
||||
|
||||
Reference in New Issue
Block a user