feat: rapid/blitz ratings, instant addplayer, hyperlinks, bug fixes
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 7s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 7s
- fetch_fide_player: parse rapid and blitz ratings from FIDE profile - subscriptions: add rapid/blitz columns (auto-migration) - /myplayers card: show rapid (📍) and blitz (⚡) ratings - /addplayer confirmation: show rapid/blitz alongside standard rating - find_player_in_cache: extract instant SQL-only search from scan_for_player - /addplayer: use find_player_in_cache instead of full HTTP scan — instant response - tournament names in /myplayers and /addplayer are now hyperlinks - add_player_watch: ON CONFLICT DO UPDATE to reactivate deleted players - my_players: try/except per card with plain-text fallback, error logged to stderr Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
39f1f7e857
commit
66b10d9425
2 changed files with 92 additions and 43 deletions
|
|
@ -89,6 +89,8 @@ def _get_conn():
|
|||
# Migrate existing tables that may be missing columns added in later versions
|
||||
_migrate_add_column(conn, 'subscriptions', 'start_date', "TEXT DEFAULT ''")
|
||||
_migrate_add_column(conn, 'subscriptions', 'end_date', "TEXT DEFAULT ''")
|
||||
_migrate_add_column(conn, 'subscriptions', 'rapid', "INTEGER DEFAULT 0")
|
||||
_migrate_add_column(conn, 'subscriptions', 'blitz', "INTEGER DEFAULT 0")
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
|
|
@ -121,12 +123,22 @@ def fetch_fide_player(fide_id: int) -> dict:
|
|||
if m:
|
||||
fed = m.group(1)
|
||||
|
||||
# Rating (standard): <p>2841</p><p ...>STANDARD
|
||||
# Ratings: <p>2841</p><p ...>STANDARD / RAPID / BLITZ
|
||||
rating = 0
|
||||
m = re.search(r'<p>(\d{3,4})</p>\s*<p[^>]*>\s*STANDARD', html)
|
||||
if m:
|
||||
rating = int(m.group(1))
|
||||
|
||||
rapid = 0
|
||||
m = re.search(r'<p>(\d{3,4})</p>\s*<p[^>]*>\s*RAPID', html)
|
||||
if m:
|
||||
rapid = int(m.group(1))
|
||||
|
||||
blitz = 0
|
||||
m = re.search(r'<p>(\d{3,4})</p>\s*<p[^>]*>\s*BLITZ', html)
|
||||
if m:
|
||||
blitz = int(m.group(1))
|
||||
|
||||
# Title: <div class="profile-info-title "><p>Grandmaster</p>
|
||||
title = ''
|
||||
m = re.search(r'<div\s+class="profile-info-title[^"]*"\s*>\s*<p>(.*?)</p>', html)
|
||||
|
|
@ -142,6 +154,7 @@ def fetch_fide_player(fide_id: int) -> dict:
|
|||
|
||||
return {
|
||||
'fide_id': fide_id, 'name': name, 'rating': rating,
|
||||
'rapid': rapid, 'blitz': blitz,
|
||||
'fed': fed, 'title': title,
|
||||
}
|
||||
|
||||
|
|
@ -156,13 +169,19 @@ def add_player_watch(user_id: int, player: dict, lang: str):
|
|||
"""
|
||||
conn = _get_conn()
|
||||
conn.execute('''
|
||||
INSERT OR IGNORE INTO subscriptions
|
||||
(user_id, fide_id, player_name, rating, fed, lang, tournament_url,
|
||||
INSERT INTO subscriptions
|
||||
(user_id, fide_id, player_name, rating, rapid, blitz, fed, lang, tournament_url,
|
||||
tournament_name, player_sno, last_results_count, last_points,
|
||||
last_round_done, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'fide_only', '', 0, 0, 0.0, 0, 1)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'fide_only', '', 0, 0, 0.0, 0, 1)
|
||||
ON CONFLICT(user_id, fide_id, tournament_url) DO UPDATE SET
|
||||
active = 1,
|
||||
player_name = excluded.player_name,
|
||||
rating = excluded.rating,
|
||||
rapid = excluded.rapid,
|
||||
blitz = excluded.blitz
|
||||
''', (user_id, player['fide_id'], player['name'], player.get('rating', 0),
|
||||
player.get('fed', ''), lang))
|
||||
player.get('rapid', 0), player.get('blitz', 0), player.get('fed', ''), lang))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
|
@ -174,12 +193,13 @@ def add_subscription(user_id: int, player: dict, tournament_url: str,
|
|||
conn = _get_conn()
|
||||
conn.execute('''
|
||||
INSERT OR REPLACE INTO subscriptions
|
||||
(user_id, fide_id, player_name, rating, fed, lang, tournament_url,
|
||||
(user_id, fide_id, player_name, rating, rapid, blitz, fed, lang, tournament_url,
|
||||
tournament_name, player_sno, last_results_count, last_points,
|
||||
last_round_done, start_date, end_date, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, 1)
|
||||
''', (user_id, player['fide_id'], player['name'], player['rating'],
|
||||
player.get('fed', ''), lang, tournament_url, tournament_name,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, 1)
|
||||
''', (user_id, player['fide_id'], player['name'], player.get('rating', 0),
|
||||
player.get('rapid', 0), player.get('blitz', 0), player.get('fed', ''),
|
||||
lang, tournament_url, tournament_name,
|
||||
player_sno, last_results_count, last_points, start_date, end_date))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
|
@ -189,7 +209,7 @@ def get_user_subs(user_id: int) -> list:
|
|||
"""Return subscriptions grouped by player (fide_id)."""
|
||||
conn = _get_conn()
|
||||
rows = conn.execute(
|
||||
'SELECT fide_id, player_name, rating, fed, tournament_name, '
|
||||
'SELECT fide_id, player_name, rating, rapid, blitz, fed, tournament_name, '
|
||||
'last_points, last_round_done, tournament_url FROM subscriptions '
|
||||
'WHERE user_id = ? AND active = 1 ORDER BY fide_id, id',
|
||||
(user_id,)).fetchall()
|
||||
|
|
@ -197,20 +217,21 @@ def get_user_subs(user_id: int) -> list:
|
|||
|
||||
players: dict = {}
|
||||
for r in rows:
|
||||
fide_id, pname, rating, fed, tname, pts, rd, url = r
|
||||
fide_id, pname, rating, rapid, blitz, fed, tname, pts, rd, url = r
|
||||
if fide_id not in players:
|
||||
players[fide_id] = {
|
||||
'fide_id': fide_id, 'player_name': pname,
|
||||
'rating': rating, 'fed': fed or '',
|
||||
'rating': rating, 'rapid': rapid or 0, 'blitz': blitz or 0,
|
||||
'fed': fed or '',
|
||||
'tournaments': [],
|
||||
}
|
||||
if url == 'fide_only':
|
||||
players[fide_id]['tournaments'].append({
|
||||
'name': None, 'points': pts, 'round_done': rd,
|
||||
'name': None, 'url': None, 'points': pts, 'round_done': rd,
|
||||
})
|
||||
else:
|
||||
players[fide_id]['tournaments'].append({
|
||||
'name': tname or url, 'points': pts, 'round_done': rd,
|
||||
'name': tname or url, 'url': url, 'points': pts, 'round_done': rd,
|
||||
})
|
||||
return list(players.values())
|
||||
|
||||
|
|
@ -578,27 +599,16 @@ def discover_max_tnr() -> int:
|
|||
return tnr
|
||||
|
||||
|
||||
def scan_for_player(fide_id: int, max_tnr: int = None,
|
||||
limit: int = 200) -> list:
|
||||
"""Scan tournaments for a player by FIDE ID.
|
||||
|
||||
Scans multiple TNR windows since chess-results numbering has sparse clusters.
|
||||
Returns list of {tnr, url, name, start_date, end_date, sno, player_name, fide_id}.
|
||||
"""
|
||||
import time
|
||||
|
||||
results = []
|
||||
seen_tnrs = set()
|
||||
|
||||
# Phase 1: check full tnr_cache via SQL (single query, instant). Return if found.
|
||||
def find_player_in_cache(fide_id: int) -> list:
|
||||
"""Instant SQL-only search. Returns same format as scan_for_player."""
|
||||
conn = _get_conn()
|
||||
pattern = f'%"{fide_id}"%'
|
||||
rows = conn.execute(
|
||||
'SELECT tnr, name, start_date, end_date, players_json FROM tnr_cache '
|
||||
'WHERE players_json LIKE ? '
|
||||
'ORDER BY tnr DESC',
|
||||
'WHERE players_json LIKE ? ORDER BY tnr DESC',
|
||||
(pattern,)).fetchall()
|
||||
conn.close()
|
||||
results = []
|
||||
for row in rows:
|
||||
tnr, tname, tstart, tend, players_json = row
|
||||
players = {int(k): v for k, v in json.loads(players_json).items()}
|
||||
|
|
@ -617,6 +627,23 @@ def scan_for_player(fide_id: int, max_tnr: int = None,
|
|||
})
|
||||
if results:
|
||||
set_tnr_state('max_tnr_seen', str(max(r['tnr'] for r in results)))
|
||||
return results
|
||||
|
||||
|
||||
def scan_for_player(fide_id: int, max_tnr: int = None,
|
||||
limit: int = 200) -> list:
|
||||
"""Scan tournaments for a player by FIDE ID (SQL cache + HTTP fallback).
|
||||
|
||||
Used by background jobs. For user-facing flows use find_player_in_cache().
|
||||
"""
|
||||
import time
|
||||
|
||||
results = []
|
||||
seen_tnrs = set()
|
||||
|
||||
# Phase 1: SQL cache (instant)
|
||||
results = find_player_in_cache(fide_id)
|
||||
if results:
|
||||
return results
|
||||
|
||||
# Phase 2: cache miss — fall back to sequential TNR scan
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue