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).
This commit is contained in:
parent
cf7fafd2ba
commit
bbbfe61094
11 changed files with 1432 additions and 488 deletions
128
test_round_by_round.py
Normal file
128
test_round_by_round.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#!/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}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue