ChessCalcNextTour/test_round_by_round.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

128 lines
4.1 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
"""Test round-by-round: parse once, truncate results, calculate next round."""
import sys
sys.path.insert(0, '.')
from swiss_calc.parser import fetch_tournament, fetch_url, parse_round_pairings
from swiss_calc.trf_generator import generate_trf
from swiss_calc.bbp_wrapper import call_bbp
import re
def norm(n):
return re.sub(r'\s+', ' ', n.replace(',', '')).strip()
BASE = 'https://s1.chess-results.com/tnr1289471.aspx?lan=11'
print("=" * 60)
print("ТЕСТ: пошаговый расчёт всех туров")
print("=" * 60)
# Parse once — get full data with correct SNo, byes, forfeits
data = fetch_tournament(BASE)
total_rounds = data['current_round']
standings = data['standings']
print(f"Турнир: {data['name'][:60]}")
print(f"Сыграно туров: {total_rounds}")
print(f"Участников: {len(standings)}")
# Collect actual pairs for comparison
name_to_sno = {norm(s['name']): s['starting_sno'] for s in standings}
actual_pairs = {}
for rd in range(1, total_rounds + 1):
html = fetch_url(f'{BASE}&art=2&rd={rd}&turdet=YES')
games = parse_round_pairings(html, rd)
for g in games:
if g['white_sno'] == 0:
g['white_sno'] = name_to_sno.get(norm(g['white_name']), 0)
if g['black_sno'] == 0:
g['black_sno'] = name_to_sno.get(norm(g['black_name']), 0)
actual_pairs[rd] = games
print(f" Тур {rd}: {len(games)} пар")
print(f"\n{'='*60}")
print(f"ПОШАГОВЫЙ РАСЧЁТ")
print(f"{'='*60}")
overall_matched = 0
overall_total = 0
for next_rd in range(2, total_rounds + 1):
completed = next_rd - 1
# Truncate results to only first `completed` rounds
truncated_standings = []
for s in standings:
full_results = s.get('results', [])
full_orig = s.get('_orig_results', [])
# Take results only up to round `completed`
trunc_results = [r for r in full_results if r.get('round', 99) <= completed]
trunc_orig = [r for r in full_orig if isinstance(r, dict) and r.get('round', 99) <= completed]
points = sum(r['score'] for r in trunc_results)
truncated_standings.append({
'rank': s['rank'],
'name': s['name'],
'fed': s['fed'],
'points': points,
'starting_sno': s['starting_sno'],
'rating': s.get('rating', 0),
'results': trunc_results,
'_orig_results': trunc_orig,
'tb': s.get('tb', []),
})
truncated = {
'name': data['name'],
'num_rounds': total_rounds,
'current_round': completed,
'players': data['players'],
'standings': truncated_standings,
}
trf = generate_trf(truncated, next_rd, name=data.get('name', 'Test'),
use_rank=True, initial_color_white=True)
try:
bbp_pairs, _ = call_bbp(trf)
except Exception as e:
print(f" Тур {next_rd}: bbpPairings ОШИБКА — {e}")
continue
# Compare with actual
calc_set = set()
for w_sno, b_sno in bbp_pairs:
if b_sno == 0 or w_sno == 0:
continue
calc_set.add((min(w_sno, b_sno), max(w_sno, b_sno)))
real_set = set()
for g in actual_pairs.get(next_rd, []):
w, b = g['white_sno'], g['black_sno']
if w == 0 or b == 0:
continue
# Only include completed games (not future pairings)
if g.get('white_score') is not None:
real_set.add((min(w, b), max(w, b)))
matched = len(calc_set & real_set)
total = len(real_set)
if total == 0:
print(f" Тур {next_rd}: нет сыгранных пар для сравнения")
continue
pct = 100 * matched / total
overall_matched += matched
overall_total += total
bar = '' * int(pct / 5) + '' * (20 - int(pct / 5))
print(f" Тур {next_rd}: {matched}/{total} совпадений [{bar}] {pct:.0f}%")
print(f"\n{'='*60}")
if overall_total > 0:
print(f"ИТОГО: {overall_matched}/{overall_total} ({100*overall_matched/overall_total:.0f}%)")
else:
print(f"ИТОГО: 0 пар")
print(f"{'='*60}")