Previously LiveEventsParser returned 0 events from /su/live because two
real differences between the live page and the pre-match listing weren't
handled:
1. Live rows omit data-event-path entirely. They expose only
data-event-treeId, and the bookmaker routes live events under
/su/live/<treeId> rather than /su/betting/<...>.
2. The closest data-sport-treeId ancestor on the live page is a
category-tree wrapper (26418=Football-live, 45356=Basketball-live, …)
instead of the canonical breadcrumb sport ID (11/6/22723/43658) the
rest of the app uses. The pre-match listing carries the canonical
ID directly.
Changes:
* EventListingParserBase.ParseRow: data-event-path becomes optional. For
live rows we synthesize EventPath = "live/<treeId>" from
data-event-treeId (validated as digits-only). Pre-match validation is
unchanged.
* New ExtractSportCodeFromLive walks ancestors looking for a sport-tree
ID and maps it through a small live-id → canonical-id table covering
the four scoped sports. Out-of-scope sports (cybersport, volleyball,
table tennis) are intentionally left unmapped — they keep their raw
category ID and the UI renders them via SportLabels as "Sport <N>".
* MarathonbetScraper.ResolveEventDetailPath: dispatches between
/su/live/<treeId> and /su/betting/<...> based on the EventPath prefix.
Removes the duplicated path-building between ScrapeEventOddsAsync and
ScrapeEventResultAsync.
* New regression tests covering all three behaviors against a real
/su/live capture (16 events, 5 sport categories).
Also: rewrites the stale "Disabled until Phase 8" hint copy on the
Settings.Workers.ResultsPollerEnabled flag — Phase 8 shipped, so the
results poller is safe to enable.