83 lines
3.1 KiB
Markdown
83 lines
3.1 KiB
Markdown
# Phase 8: Results Loader
|
|
|
|
**Status:** ⬜ Not Started
|
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
|
**Domain:** fullstack
|
|
**Implementer:** Sonnet (backend) + Opus (UI)
|
|
**Depends on:** Phase 6 (UI patterns)
|
|
|
|
## Objective
|
|
|
|
Per customer TZ §4: scrape and persist results of completed events, with a UI that
|
|
allows the user to load all results in a date range OR pick specific events to load
|
|
selectively.
|
|
|
|
## Tasks
|
|
|
|
### Backend (Sonnet)
|
|
|
|
- [ ] `PullResultsUseCase` was scaffolded in Phase 4 — extend it here:
|
|
- When `selection` is null/empty, fetch results for ALL completed events in range
|
|
that don't have a stored `EventResult` yet
|
|
- When `selection` provided, fetch results only for those events
|
|
- Idempotent — re-running for already-loaded results is a no-op
|
|
- [ ] Add `IResultsScraper`-related parser methods (or extend `IOddsScraper` with
|
|
`ScrapeResultsAsync`) — implementation may already exist from Phase 3.
|
|
- [ ] After persisting results, infer `WinnerSide` and update the `Event` accordingly
|
|
(or store derived `WinnerSide` on `EventResult` only — implementer's choice, document
|
|
in handoff).
|
|
- [ ] Tests in `Marathon.Application.Tests`:
|
|
- `PullResultsUseCase` with selection list pulls only those events
|
|
- With null selection, pulls all completed events missing results in range
|
|
- Idempotency: running twice produces no duplicates
|
|
|
|
### Frontend (Opus + frontend-design)
|
|
|
|
- [ ] Create `Marathon.UI/Pages/Results/ResultsLoader.razor`:
|
|
- Date range picker
|
|
- Two modes: "All in range" (default) | "Selected events"
|
|
- Selected events mode: searchable multi-select of completed events lacking results
|
|
- "Load Results" button → invokes `PullResultsUseCase`
|
|
- Progress indicator (number of events processed / total)
|
|
- Result table on completion showing what was loaded (event identity, score,
|
|
winner side)
|
|
- [ ] Create `Marathon.UI/Pages/Results/ResultsList.razor`:
|
|
- Browse already-loaded results
|
|
- Filter by sport, date range, winner-side-1 / winner-side-2 / draw
|
|
- Link back to event detail page (Phase 6)
|
|
- [ ] Add `Results` entry to navigation drawer.
|
|
- [ ] Localize all strings RU + EN.
|
|
- [ ] Frontend tests:
|
|
- bUnit: loader page invokes use case with correct parameters in both modes
|
|
- bUnit: results list filter narrows correctly
|
|
|
|
## Files to Modify/Create
|
|
|
|
- `src/Marathon.Application/UseCases/PullResultsUseCase.cs` — extend
|
|
- `src/Marathon.UI/Pages/Results/ResultsLoader.razor`
|
|
- `src/Marathon.UI/Pages/Results/ResultsList.razor`
|
|
- `tests/Marathon.Application.Tests/UseCases/PullResultsUseCaseTests.cs`
|
|
- `tests/Marathon.UI.Tests/Pages/Results/**`
|
|
|
|
## Acceptance Criteria
|
|
|
|
- Compiles (Big Bang).
|
|
- Selective loading respects user's selection.
|
|
- Bulk loading skips events that already have results.
|
|
- UI shows progress during a multi-event load.
|
|
|
|
## Notes
|
|
|
|
- Big Bang: compile-only smoke check.
|
|
|
|
## Review Checklist
|
|
|
|
- [ ] Idempotent — no duplicate `EventResult` rows
|
|
- [ ] UI handles empty range gracefully (no events match)
|
|
- [ ] All strings localized
|
|
|
|
## Handoff to Next Phase
|
|
|
|
<!-- Filled by Phase 8 implementer. Phase 9 is packaging — note any runtime requirements
|
|
(e.g., Playwright browser binaries) that need to be bundled with the installer. -->
|