fix: wrong player highlighted and wrong pairings due to FIDE ID / SNo mismatch
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 8s

- tracker.py _parse_art0_page: stop at first 6-10 digit number as FIDE ID;
  national IDs (also 6-10 digits) no longer overwrite it. FED/rating now
  scanned forward past the national ID column.
- parser.py fetch_tournament: fuzzy start-list name matching no longer
  overrides starting_sno from standings column 0. Fuzzy match is kept only
  for rating lookup. Exact name match still may override SNo (reliable).
- tracker.py rescan_existing_tournaments: after each art=0 re-fetch, update
  player_sno in subscriptions whose FIDE ID appears at a different SNo than
  stored (corrects drift caused by the old parsing bug).

Root cause: national ID of Gorshkov (SNo=26 in Odintsovo Blitz) matched
Vrubel's FIDE ID in the cache; subscription was created with wrong player_sno=26.
DB already patched directly: Vrubel→sno=39, Aslanbekov→sno=40.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
vrubel 2026-06-22 11:05:43 +00:00
parent 8058080c3a
commit e64e436b6f
2 changed files with 43 additions and 21 deletions

View file

@ -524,14 +524,21 @@ def _parse_art0_page(html: str) -> dict:
rating = 0
name = ''
for ci, t in enumerate(texts):
if re.match(r'^\d{6,10}$', t):
# Only capture the FIRST 6-10 digit number as FIDE ID.
# Subsequent matches are national IDs — must not overwrite.
if re.match(r'^\d{6,10}$', t) and not fide_id:
fide_id = int(t)
if ci + 1 < len(texts) and re.match(r'^[A-Z]{3}$', texts[ci + 1]):
fed = texts[ci + 1]
if ci + (2 if fed else 1) < len(texts):
rt = texts[ci + (2 if fed else 1)]
if rt.isdigit():
rating = int(rt)
# Scan forward for 3-letter FED code (skipping national ID)
for look in range(1, min(5, len(texts) - ci)):
nt = texts[ci + look]
if re.match(r'^[A-Z]{3}$', nt):
fed = nt
# Rating is right after FED
if ci + look + 1 < len(texts):
rt = texts[ci + look + 1]
if rt.isdigit() and int(rt) <= 4000:
rating = int(rt)
break
if ci >= 1 and re.search(r'[A-Za-zА-Яа-я]', t) and len(t) > 2:
if not name:
name = t
@ -818,6 +825,21 @@ async def rescan_existing_tournaments(context):
continue # tournament deleted or unreachable
new_fids = set(new_data['players'].keys())
# Correct player_sno for subscriptions whose SNo drifted from the cache
tnr_url_frag = f'tnr{tnr}.aspx'
for fid in new_fids:
if fid not in fide_to_users:
continue
correct_sno = new_data['players'][fid]['sno']
conn = _get_conn()
conn.execute(
'UPDATE subscriptions SET player_sno = ? '
'WHERE fide_id = ? AND tournament_url LIKE ? '
'AND player_sno != ? AND active = 1',
(correct_sno, fid, f'%{tnr_url_frag}%', correct_sno))
conn.commit()
conn.close()
# New players that appeared since last cached snapshot
appeared = new_fids - old_fids
for fid in appeared: