2 Commits

Author SHA1 Message Date
b813ed5f81 Merge pull request 'fix(teams): resolve dividers from tab name + full title, not last notice row' (#9) from feat/teamsync-reposition into master
All checks were successful
Test & Deploy / test (push) Successful in 5s
Test & Deploy / deploy (push) Successful in 7s
Reviewed-on: #9
2026-09-03 20:35:24 +00:00
Rene Arumetsa
c19e67b5ab 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 "<tab name> <section title>",
  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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPRsW4tazVtYi2jNzVQkre
2026-09-03 23:32:58 +03:00
2 changed files with 25 additions and 2 deletions

View File

@@ -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)) 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 name_col is None or lineup_col is None:
if (text := _merged_title(rows[i])) is not 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 i += 1
continue continue
rosters: dict[str, list[str]] = {} rosters: dict[str, list[str]] = {}
@@ -444,7 +448,10 @@ def _refresh_teams_sync() -> dict[str, list[str]]:
dividers: dict[str, int] = {} dividers: dict[str, int] = {}
for ws in spreadsheet.worksheets(): for ws in spreadsheet.worksheets():
for section in parse_team_sections(ws.get_all_values()): 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(): for team, players in section.rosters.items():
rosters.setdefault(team, []).extend(players) rosters.setdefault(team, []).extend(players)
if divider: if divider:

View File

@@ -142,6 +142,22 @@ def test_resolve_divider_no_config_or_no_title_is_none():
assert sheets.resolve_divider("", DIVIDERS) 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) ------------------------ # --- plan_team_positions (pure role-ordering maths) ------------------------
def test_plan_moves_teams_directly_under_their_divider(): def test_plan_moves_teams_directly_under_their_divider():