From c19e67b5ab62f675cecf9a36ab819dfc33f2c2f3 Mon Sep 17 00:00:00 2001 From: Rene Arumetsa Date: Thu, 3 Sep 2026 23:32:58 +0300 Subject: [PATCH] fix(teams): resolve dividers from tab name + full title, not last notice row Section titles are stacked single-cell rows: the "TipiLAN 2026 CS2" title sits above notice rows ("If a team withdraws..."), and parse_team_sections kept only the LAST one, so the notice clobbered the title and resolve_divider saw no game/year keywords -> None -> teams never positioned. - parse_team_sections now accumulates all single-cell rows above a header, so the game/year title survives alongside the notices. - _refresh_teams_sync resolves the divider from "
", so the game is taken reliably from the CS2/LoL worksheet name while the year still comes from the title, keeping year-scoped TEAM_DIVIDER_*_2026 vars. - regression test: title survives notice rows and still resolves. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XPRsW4tazVtYi2jNzVQkre --- core/sheets.py | 11 +++++++++-- tests/test_team_sync.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/core/sheets.py b/core/sheets.py index d490895..8eba97c 100644 --- a/core/sheets.py +++ b/core/sheets.py @@ -362,7 +362,11 @@ def parse_team_sections(rows: list[list]) -> list[TeamSection]: lineup_col = _find_col(rows[i], lambda c: c.strip().lower().startswith(_LINEUP_HEADER_PREFIX)) if name_col is None or lineup_col is None: if (text := _merged_title(rows[i])) is not None: - title = text + # Accumulate every single-cell row above the header, not just the + # last one: sheets stack the "TipiLAN 2026 CS2" title above notice + # rows ("If a team withdraws..."), and the later notices must not + # clobber the title whose keywords resolve_divider needs. + title = f"{title} {text}".strip() if title else text i += 1 continue rosters: dict[str, list[str]] = {} @@ -444,7 +448,10 @@ def _refresh_teams_sync() -> dict[str, list[str]]: dividers: dict[str, int] = {} for ws in spreadsheet.worksheets(): for section in parse_team_sections(ws.get_all_values()): - divider = resolve_divider(section.title) + # Match on the tab name too ("CS2"/"LoL"): it names the game reliably + # even when the game/year title row is shadowed by notice rows, while + # the section title still supplies the year for year-scoped dividers. + divider = resolve_divider(f"{ws.title} {section.title}") for team, players in section.rosters.items(): rosters.setdefault(team, []).extend(players) if divider: diff --git a/tests/test_team_sync.py b/tests/test_team_sync.py index 675a7f9..9dc740a 100644 --- a/tests/test_team_sync.py +++ b/tests/test_team_sync.py @@ -142,6 +142,22 @@ def test_resolve_divider_no_config_or_no_title_is_none(): assert sheets.resolve_divider("", DIVIDERS) is None +def test_title_survives_notice_rows_below_it(): + # Real sheets stack the game/year title above single-cell notice rows; the + # title's keywords must survive so resolve_divider still matches (regression: + # notice rows used to overwrite the title, yielding a None divider). + rows = [ + ["TipiLAN 2026 CS2 Registration Log"] + [""] * 4, + ["This log is updated automatically."] + [""] * 4, + ["If a team from the Top 32 withdraws, ..."] + [""] * 4, + ["No", "Team Name", "Lineup (nickname, citizenship)", "", ""], + ["1", "GENESIS", "kapa (EST)", "", ""], + ] + section = sheets.parse_team_sections(rows)[0] + assert "CS2" in section.title and "2026" in section.title + assert sheets.resolve_divider(section.title, DIVIDERS) == 1498736834656604251 + + # --- plan_team_positions (pure role-ordering maths) ------------------------ def test_plan_moves_teams_directly_under_their_divider():