fix(scraping): live page lacks data-event-path and uses category sport IDs

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.
This commit is contained in:
2026-05-09 16:07:03 +03:00
parent 537b78ab83
commit 004dbeae8b
8 changed files with 20459 additions and 45 deletions
+4 -3
View File
@@ -60,7 +60,8 @@
.m-app-frame {
display: grid;
grid-template-rows: 60px 1fr 36px;
min-height: 100vh;
height: 100vh;
overflow: hidden;
}
.m-appbar {
@@ -70,8 +71,6 @@
padding: 0 clamp(var(--m-space-3), 2vw, var(--m-space-5));
border-bottom: 1px solid var(--m-c-rule);
background: var(--m-c-paper);
position: sticky;
top: 0;
z-index: 10;
}
@@ -82,6 +81,8 @@
position: relative;
z-index: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
/* MudDrawer is positioned fixed/absolute by Mud's CSS — push main content
@@ -117,7 +117,7 @@
<data name="Settings.Workers.LivePollIntervalSeconds"><value>Live poll interval (sec)</value></data>
<data name="Settings.Workers.LivePollIntervalSeconds.Hint"><value>Delay between live-odds polling cycles. Default 30 s.</value></data>
<data name="Settings.Workers.ResultsPollerEnabled"><value>Results poller enabled</value></data>
<data name="Settings.Workers.ResultsPollerEnabled.Hint"><value>Disabled until Phase 8. Enable only after match-complete polling is implemented.</value></data>
<data name="Settings.Workers.ResultsPollerEnabled.Hint"><value>Polls each tracked event's detail page until matchIsComplete=true and records the final score. Safe to enable.</value></data>
<data name="Settings.Workers.ResultsPollIntervalSeconds"><value>Results poll interval (sec)</value></data>
<data name="Settings.Workers.AnomalyDetectionEnabled"><value>Anomaly detection enabled</value></data>
<data name="Settings.Workers.AnomalyDetectionEnabled.Hint"><value>Runs the suspension-flip detector on every cycle. Disable to pause analysis without losing collected snapshots.</value></data>
@@ -123,7 +123,7 @@
<data name="Settings.Workers.LivePollIntervalSeconds"><value>Интервал лайв-опроса (сек)</value></data>
<data name="Settings.Workers.LivePollIntervalSeconds.Hint"><value>Пауза между циклами сбора лайв-котировок. По умолчанию 30 с.</value></data>
<data name="Settings.Workers.ResultsPollerEnabled"><value>Сборщик результатов включён</value></data>
<data name="Settings.Workers.ResultsPollerEnabled.Hint"><value>Отключён до Phase 8. Включите только после реализации опроса match-complete.</value></data>
<data name="Settings.Workers.ResultsPollerEnabled.Hint"><value>Опрашивает страницу события до matchIsComplete=true и сохраняет итоговый счёт. Можно включать.</value></data>
<data name="Settings.Workers.ResultsPollIntervalSeconds"><value>Интервал сборщика результатов (сек)</value></data>
<data name="Settings.Workers.AnomalyDetectionEnabled"><value>Детектор аномалий включён</value></data>
<data name="Settings.Workers.AnomalyDetectionEnabled.Hint"><value>Запускает детектор разворота после паузы на каждом цикле. Отключение приостанавливает анализ без потери накопленных снимков.</value></data>