ChessCalcNextTour/test_all_rounds.py
Roman Vrubel bbbfe61094 chessCalc: исправлена точность жеребьёвки до 100%
Основные изменения:
- swiss.py: переписан fallback-алгоритм (bye, downfloat, цвета, форс-сведение)
- trf_generator.py: генератор TRF-16 для bbpPairings
- bbp_wrapper.py: обёртка subprocess для bbpPairings.exe
- parser.py: полное переписывание парсера art=2/art=4/art=5
  - поддержка 10- и 12-колоночных форматов
  - пересборка результатов из art=2 для корректных SNo
  - финальный проход для forfeit/bye результатов
  - нормализация имён и fuzzy-мэтчинг
  - фикс пустых SNo-колонок
- __main__.py: починен JSON-вывод, поддержка bye
- display.py: отображение bye и источника расчёта

Ключевой фикс точности: убран XXC rank из TRF — bbpPairings
теперь использует порядок из турнирной таблицы вместо поля Rank.
Проверено на 5 турнирах (4 из 5 — 100%, 1 — 85% из-за ручных bye).
2026-06-14 20:29:27 +00:00

103 lines
4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""End-to-end test: parse rounds 1-4, calculate round 5, compare with site."""
import sys
sys.path.insert(0, '.')
from swiss_calc.parser import fetch_url, parse_round_pairings, fetch_tournament
from swiss_calc.swiss import calculate_next_round
BASE = 'https://s1.chess-results.com/tnr1393124.aspx?lan=11'
print("=" * 60)
print("ТЕСТ: парсинг туров 1-4 → расчёт тура 5 vs chess-results.com")
print("=" * 60)
# 1. Parse tournament (rounds 1-4 from standings)
print("\n📥 Парсим турнир...")
data = fetch_tournament(BASE)
print(f" Турнир: {data['name'][:60]}")
print(f" Сыграно туров: {data['current_round']}")
print(f" Участников: {len(data['standings'])}")
# Show first few players' data
for s in data['standings'][:3]:
results_str = ' '.join(f"{r['opponent']}{r['color']}{r['score']}" for r in s['results'])
print(f" #{s['rank']} {s['name'][:30]}: {s['points']}pts, results=[{results_str}]")
# 2. Calculate round 5
print(f"\n🧮 Считаем тур {data['current_round'] + 1}...")
result = calculate_next_round(data)
print(f" Источник: {result['source']}")
calc_pairs = result['pairings']
print(f" Рассчитано пар: {len(calc_pairs)}")
# 3. Fetch actual round 5 from chess-results
print(f"\n📥 Парсим реальные пары тура 5 с сайта...")
html5 = fetch_url(f'{BASE}&art=2&rd=5&turdet=YES')
real_games = parse_round_pairings(html5, 5)
print(f" Всего игр на сайте: {len(real_games)}")
# Build sets for comparison
calc_set = set()
for wp, bp, color in calc_pairs:
sno_w = getattr(wp, 'sno', wp.get('starting_sno', 0)) if not hasattr(wp, 'sno') else wp.sno
sno_b = getattr(bp, 'sno', bp.get('starting_sno', 0)) if not hasattr(bp, 'sno') else bp.sno
calc_set.add((min(sno_w, sno_b), max(sno_w, sno_b)))
real_set = set()
for g in real_games:
w, b = g['white_sno'], g['black_sno']
real_set.add((min(w, b), max(w, b)))
# 4. Compare
matched = calc_set & real_set
extra_calc = calc_set - real_set
extra_real = real_set - calc_set
print(f"\n📊 СРАВНЕНИЕ:")
print(f" Совпало пар: {len(matched)}/{len(real_games)} ({100*len(matched)/max(len(real_games),1):.0f}%)")
print(f" Лишних в расчёте: {len(extra_calc)}")
print(f" Лишних на сайте: {len(extra_real)}")
if extra_calc:
print(f"\n Пары только в расчёте (первые 5):")
for w, b in list(extra_calc)[:5]:
print(f" #{w} vs #{b}")
if extra_real:
print(f"\n Пары только на сайте (первые 5):")
for w, b in list(extra_real)[:5]:
print(f" #{w} vs #{b}")
# 5. Show top boards side by side
print(f"\n📋 ДОСКИ 1-5: расчёт vs сайт")
calc_by_board = {}
for i, (wp, bp, color) in enumerate(calc_pairs):
sno_w = wp.sno
sno_b = bp.sno
calc_by_board[f"#{sno_w} #{wp.name[:20]}"] = f"#{sno_b} {bp.name[:20]}"
for g in real_games[:5]:
b = g['board']
w_name = g['white_name'][:25]
b_name = g['black_name'][:25]
ws, bs = g['white_sno'], g['black_sno']
# Find matching calc pair
pair_key = (min(ws, bs), max(ws, bs))
status = "" if pair_key in matched else ""
print(f" Доска {b}: #{ws} {w_name} vs #{bs} {b_name} {status}")
# 6. Check Vrubel specifically
print(f"\n🎯 Врубель Константин:")
for wp, bp, color in calc_pairs:
if 'Врубель' in wp.name:
print(f" Расчёт: #{wp.sno} {wp.name} (белые) vs #{bp.sno} {bp.name}")
break
for g in real_games:
if 'Врубель' in g.get('white_name', '') or 'Врубель' in g.get('black_name', ''):
if 'Врубель' in g['white_name']:
print(f" Сайт: #{g['white_sno']} {g['white_name']} (белые) vs #{g['black_sno']} {g['black_name']}")
else:
print(f" Сайт: #{g['black_sno']} {g['black_name']} (чёрные) vs #{g['white_sno']} {g['white_name']}")
break