Tennis Entry List
Overview
Given one or more spaziotennis entry-list article URLs (typically several
concurrent events at once), generate a self-contained, interactive HTML
dashboard. Each page is its own data source — it already contains every list
with IN / OUT colour markers, so no second site is needed.
For each tournament, for 正赛 (Main Draw) and 资格赛 (Qualifying):
- 入围名单 (Entry List) — split into two parts:
- top: players still in (includes
INalternates promoted in, shown in blue,
and any alternate promoted by a click, shown in green); - bottom:
OUT/ withdrawn players (red strike-through). - Ordered by 当前排名 (current live ranking), ascending.
- 候补名单 (Alternates) — split into two parts:
- top: alternates with no
IN/OUTmarker (the promotable pool); - bottom:
OUTalternates (red strike-through). INalternates are moved out of this list into the entry list above.- Ordered by 报名用排名 (entry/acceptance ranking), ascending.
- Two ranking columns in every table:
报名排名(entry_rank, from the
spaziotennis input URL) and当前排名(live_rank, from live-tennis.eu, matched
by player name). See "Current vs Entry Ranking" below. - 备注列 (Notes column) — present in every table. See Cross-Event Notes.
Cross-Event Notes (核心需求)
When the user supplies several events at the same time, the script cross-checks
players across them and writes a 备注 note into the relevant player's cell
(no separate summary box). Three rules, all in compute_cross_notes:
- 选择了 B赛事 — a player
OUT(alternates) or withdrawn (entries) in
tournament A, butIN(alternates) or in the draw (entries) in tournament
B. The 备注 cell in A reads选择了 B赛事(B's short label, e.g.
ATP 250 HANGZHOU 2026). This is the "球员在 A 是 OUT、在 B 是 IN → A 备注选了 B" rule. - 报名了 B赛事 — a player appears as an alternate with no
IN/OUT
marker (status=none) in multiple tournaments. Every such tournament notes
the others, e.g.报名了 B赛事in A (and报名了 A赛事in B). - 还未做出选择 — a player is
IN(already promoted into the entry list) in
tournament A, but still an alternate with noOUTmarker (status=none,
still in the alternate pool, not withdrawn) in tournament B. Although the
player has entered A's entry list, the 备注 cell in A reads
还未做出选择(they haven't given up on B yet).
A single player cell may combine fragments with ; when several rules apply
(e.g. out in A, in B and C → 选择了 B赛事;选择了 C赛事).
When to use
- The user pastes one or more spaziotennis
entry-list-*article URLs and asks
for the entry lists, alternates, withdrawals, qualifying lists, etc. - Multiple URLs = multiple concurrent tournaments; the dashboard shows them all
and links players across events via the 备注 column. - The user wants two distinct rankings: the entry/acceptance ranking
(报名用排名, from the spaziotennis URL) and the current live ranking
(当前排名, from live-tennis.eu), with the entry list sorted by current rank
and alternates sorted by entry rank. - Trigger phrases: "生成入围名单", "entry list", "候补名单", "退赛名单",
"资格赛名单", "谁退赛了", "同时生成两个比赛清单", "跨赛事备注", "当前排名",
"报名排名", "按排名排序", any URL shaped like the example below, or a bare
tournament name with no URL such as "ATP 北京", "ATP 东京", "ATP Hangzhou
2026" (the agent then auto-discovers the spaziotennis entry-list page).
Input
One or more spaziotennis entry-list article URLs, space-separated, e.g.
https://www.spaziotennis.com/trn/ent/entry-list-atp-hangzhou-2026-partecipanti-ed-italiani-presenti/141417
(the trailing numeric segment is the article id and is ignored; a #fragment
such as #google_vignette is also ignored). A saved local copy of that page
(.html) works too, for offline debugging.
Optional flags:
- --live <file.html> — use a saved live-tennis.eu ATP ranking page instead of
fetching it online (handy for offline / reproducible runs).
- --no-live — skip the current ranking entirely; the 当前排名 column is left
blank and the entry list falls back to sorting by 报名用排名.
- --calendar <file.json> — an ATP Tour calendar (list of
{name, city, category, start, end} where dates are YYYY-MM-DD). When
supplied, the script runs the 同期赛事完整性检查 and injects a banner
into the page plus prints any missing same-period event to stderr.
No-URL Auto-Discovery (无网址自动定位)
When the user supplies only tournament names (e.g. "ATP 北京", "ATP 东京",
"ATP Hangzhou 2026") and not a spaziotennis URL, do not refuse — locate the
entry-list page first, then run the workflow above. The data source is still the
spaziotennis article; this step only resolves its URL.
- WebSearch the spaziotennis entry-list page using the tournament name plus
year, e.g.spaziotennis entry list ATP Tokyo 2026. The result usually
surfaces the article directly. URL shape:
https://www.spaziotennis.com/trn/ent/entry-list-atp-<city>-<year>-partecipanti-ed-italiani-presenti/<id>
(the trailing<id>is ignored by the parser; a#fragmentis also ignored). - Mind the Italian city exonyms. spaziotennis is an Italian site; its URLs
and<h3>titles carry Italian city names, not English. If the first search
fails, search the Italian name too:
- Beijing → Pechino (entry-list-atp-pechino-2026-...)
- Tokyo → Tokio (English "tokyo" also works)
- London → Londra, Paris → Parigi, Rome → Roma, Monte Carlo → Monaco,
Munich → Monaco di Baviera, Basel → Basilea, etc.
The_CITY_ALIASESmap in the script (used by the calendar check) already
covers the common ones; if a run reports
未能从 ATP Tour 赛历中匹配到你提供赛事的日期(未匹配:X), add that exonym
to_CITY_ALIASES— the same map also helps construct/verify search queries. - Verify before parsing. Confirm the found page's title contains
ENTRY LIST(the script keys off that). If search returns only atptour.com or
third-party mirrors, keep searching for the spaziotennis article specifically
— it is the only supported data source. - Disambiguate when needed. If a city has multiple same-name events in a
year (e.g. two Tokyo events) or an unusual exonym, ask the user to confirm the
exact event (tour, week, category) or paste the spaziotennis URL directly.
Pasting the URL is always the most reliable input and avoids search ambiguity.
This capability was validated end-to-end: "ATP 北京" + "ATP 东京" (both 2026
ATP 500, 30 Sep–6 Oct) were resolved to their spaziotennis pages via search and
produced a working two-event dashboard with 17 cross-event notes.
ATP Tour Calendar Completeness Check (同期赛事完整性检查)
When the user supplies several concurrent events, they may have missed one.
atptour.com/en/tournaments is the reference (it lists only ATP Tour events:
250 / 500 / 1000 / Grand Slam / Finals / Next Gen — Challenger/ITF are separate
pages and are excluded by _is_atp_tour). The page is Cloudflare-protected and
JS-rendered, so the agent obtains the schedule (open the page in a browser,
or use an equivalent authoritative mirror of the official ATP Tour schedule) and
passes it as --calendar <file.json>.
check_missing(tourneys, calendar, tol=1) then:
1. Matches each provided tournament to a calendar entry by normalised city
(extracted from the spaziotennis title, e.g. HANGZHOU from
ENTRY LIST ATP 250 HANGZHOU 2026).
2. Builds the period window [min start, max end] from the matched dates.
3. Flags every ATP Tour event whose start ≥ window_start − 1 day and
end ≤ window_end + 1 day as same-period (the user's rule: concurrent
events differ by at most one day on both start and end).
4. Reports same-period events whose city is not among the provided ones as
missing → the banner shows ⚠️ 你可能遗漏了以下同期 ATP Tour 赛事… and
stderr prints 同期赛事检查: 缺失 N 站 -> …. If none are missing it shows
✓ 已涵盖该时段所有 ATP Tour 赛事.
City matching is exonym-aware. spaziotennis is an Italian site, so titles
carry Italian city names (ENTRY LIST ATP 500 PECHINO 2026 = Beijing), while
ATP calendars are English. _norm_city therefore maps common Italian exonyms to
the English calendar city via _CITY_ALIASES (pechino → beijing,
londra → london, monaco → monte carlo, …). If a run reports
未能从 ATP Tour 赛历中匹配到你提供赛事的日期(未匹配:X), add that exonym to
_CITY_ALIASES — otherwise the completeness check silently does nothing.
The agent should surface the missing events to the user as a reminder each run.
Workflow
- Run the bundled script with the URL(s) (or local html path(s)):
python <skill>/scripts/fetch_entry_list.py "<URL1>" "<URL2>" ... --out <output-dir>
Use the managed Python runtime:
C:\Users\hong2\.workbuddy\binaries\python\versions\3.13.12\python.exe
(stdlib only — no third-party packages needed). - The script prints the path to
entry_lists_<city1>_<city2>.htmlplus a
debug JSON, a per-tournament parse summary, and the cross-event notes found,
e.g.
正赛: 入围20(退赛4) 候补20(in 5/none 2/out 13) | 资格赛: 入围14(退赛9) 候补39(...)
and跨赛事备注: 20 条followed by each选择了 …line. - Present the generated HTML to the user (built-in browser preview; fully
interactive offline).
How the page is parsed
The article is WordPress markup: an <h3> title (ENTRY LIST <TOUR> …, where
<TOUR> varies per event) followed by <p> blocks separated by <br>, with
players carrying a colour marker:
color:#ff0000+<del>(red strike-through) → OUT (withdrawn).color:#99cc00(green) → IN (alternate that is in / already promoted).- no special colour → neither
INnorOUT.
A leading + (e.g. + Rune,Holger DEN 133) marks a player added to the list
later; it is stripped from the name and kept as plus: true. Both prefixes are
stripped before the alternate number is read, since the line reads
IN 1 Baez,Sebastian (2) ARG 51 / OUT 1 Merida,Daniel (1) ESP 39.
Important: spaziotennis often merges the main-alternates tail, the
ALTERNATES heading, the QUALIFICAZIONI heading, and all qualifying players
into a single <p>, separated only by <br>. So parse_page walks the
<h2>/<h3>/<p> blocks but classifies each <br> segment in order, tracking
the current draw (main / qualifying) and section (entries / alternates)
via the ALTERNATES / QUALIFICAZIONI / QUALIFYING heading tokens. Each
player line yields name, country, rank, bracket (档位 1/2), alt_num, pr
(保护排名), marker/status. Update parse_one_segment / parse_page if the
markup changes.
Content boundary — critical: the article body is followed by share widgets,
related-news cards (e.g. a <h2>Laver Cup, …</h2>), and the footer copyright
(Editore: VIBES MEDIA SRL, P.iva …). Those lines contain commas and would be
mis-parsed as players. parse_page therefore slices the HTML at the
<!-- CONTENT END … --> marker (present on these pages right after the last
qualifying alternate) and additionally stops on a footer/related-news keyword
regex (_STOP_RE) as a fallback. Keep both guards when maintaining the parser.
Current vs Entry Ranking (live-tennis.eu)
Two rankings are kept separate:
- 报名用排名 (
entry_rank) — the number at the end of each player line on the
spaziotennis input page (the acceptance / entry ranking used for the draw).
Stored asentry_rankin every player record. - 当前排名 (
live_rank) — the live ATP ranking from
https://live-tennis.eu/en/atp-live-ranking, fetched at generation time (the
site returns 403 to a bareurllibrequest, sofetch_live_rankingsends a
full browser header set viaLIVE_HEADERS). Parsed from each<tr>row's
<td class=rk>(rank) and<td class=pn>(name).
Fetch is flaky — retry, don't ship a blank column. live-tennis.eu also
intermittently fails with _ssl.c:1015: The handshake operation timed out / read
timeout. When the run prints
WARN: 当前排名获取失败…, do not accept the blank 当前排名 column: re-fetch
with retries (typically succeeds on the 2nd attempt) and save the page, then
re-run with --live <saved.html>, e.g.
for i in 1..5: try fetch_live_ranking() -> save _live_ranking.html; break on success
python fetch_entry_list.py "<URL>" --out <dir> --live _live_ranking.html
A healthy run prints 当前排名匹配: N/N 球员 with N == N. A result of
0/56 means the ranking page was never obtained — fix it before presenting.
Name matching (apply_live_ranking / _match_live): spaziotennis uses
Lastname,Firstname while live-tennis uses Firstname Lastname, so names are
normalised (_norm: lowercase, special-letter transliteration e.g. Turkish ı→i,
NFKD de-accenting, strip non-alphanumerics) and reordered before matching. Exact
normalised match is preferred; a subset fallback (spaziotennis tokens ⊆ live
tokens, live at most one extra token — covers missing middle names) picks the
highest-ranked candidate. Sort order: entry list by live_rank (current),
alternates by entry_rank (acceptance).
Known gotchas handled:
- NG (Next Gen) sits between country and rank (Kouame,Moise FRA NG 200); the
country scan (_COUNTRY_NOISE) skips NG so the real country isn't lost.
- Players beyond live-tennis's ~1000-row table (e.g. Lock,Benjamin) get
live_rank = null and show — in the 当前排名 column.
Promotion logic (client-side, in the HTML)
Per draw, computed from the parsed lists plus a clicked set of toggled names.
The click key is tournamentIndex::playerName so multi-tournament simulations
never interfere with each other.
- Clicking any in-entry player → toggles them withdrawn: they move to the
bottom of the entry list (red strike-through) and the top remaining
promotable alternate (lowestentry_rank, thenonegroup) is promoted into
the entry list (green "↑递补" badge) and removed from the alternates top. - One withdrawal promotes exactly one alternate; N withdrawals promote the top
N alternates inentry_rankorder. - Clicking a withdrawn / bottom player restores them, reversing the promotion.
- "重置全部" clears all changes; "演示:各赛事正赛首位退赛" withdraws the first
main entry of every tournament to show the effect.
Output
A self-contained HTML file (no server, no network after generation):
- Layout is a section-row grid so multiple tournaments line up 同行
(same row): the first column is a fixed section-label column, then one column
per tournament (2 events → 2 columns, 3 → 3, 4+ → 3 that wrap). Each row is
one section — 正赛 · 入围名单, 正赛 · 候补名单, 资格赛 · 入围名单,
资格赛 · 候补名单 — and every tournament's panel for that section sits in the
same row, aligned left-to-right. So all tournaments' 入围名单 share one row,
all 候补名单 share the next.
- The tournament title row (colour-accented ① ② ③ chips) is sticky under the
toolbar; the section-label column is sticky on the left. Each panel shows
in / withdrawn (or none / out) sub-tables and a 备注 column.
- Clickable rows, a colour legend (blue / green / red / amber-for-notes), a
reset button, and a demo button.
- Colours: blue = IN alternate already in the draw, green = click-promoted
alternate, red = withdrawn / OUT, amber = cross-event 备注 note.
Notes / debugging
- The data source is the spaziotennis input URL itself — 100% of the lists come
from it; no cross-site slug guessing to fail. - WTA / Challenger articles use the same layout and are supported identically;
only theENTRY LIST <TOUR>title text changes. - Top-seeded players may have no country code on the source page (expected).
- If a tournament's qualifying section is empty on the source page, its
qualifying panels simply show "(无)".