Files
maraphon-app/plans/initial-implementation/PLAN.md
T
alexei.dolgolyov 12208a4762 feat(phase-7-frontend): anomaly feed UI + nav badge + Settings toggle (+31 bUnit tests)
Frontend portion of Phase 7. Backend (commit a6ff368) had already shipped
the AnomalyDetector, DetectAnomaliesUseCase, AnomalyDetectionPoller, and
all DI wiring. This commit adds the user-facing surfaces.

New surfaces (Option A routing — folder-per-feature):
- Pages/Anomalies/AnomalyFeed.razor (@page /anomalies) — replaces the
  Phase 5 placeholder with a severity-coded card stream, filter chips
  (severity / sport / date), unread-count summary, 'Mark all read' action.
- Pages/Anomalies/Detail.razor (@page /anomalies/{id:guid}) — m-detail-header
  lockup + AnomalyEvidence panel + back link to /events/{eventCode}.

New components:
- AnomalyCard.razor — severity-tinted left border (signal-red on High,
  amber on Medium, neutral on Low) + SeverityBadge pill + sport icon +
  pre→post tabular-mono rate strip + relative time. Click navigates.
- SeverityBadge.razor — small pill mapping score → bucket per backend
  handoff (Low <0.45, Medium <0.60, High ≥0.60).
- AnomalyEvidence.razor — two-column pre/post panel with implied-prob
  bars + raw rates; favourite-swap callout when argmax(p_pre) ≠ argmax(p_post);
  signal-red 3px left border on the post column. Handles 2-way (no draw).

State + service split mirrors Phase 6's pattern:
- AnomalyViewModels.cs — AnomalyListItem / AnomalyDetailVm / Severity enum
  / AnomalyEvidenceSnapshot record. Severity computed in the view-model
  from Score.
- IAnomalyBrowsingService / AnomalyBrowsingService — wraps IAnomalyRepository,
  parses Anomaly.EvidenceJson into typed view-models, applies filters
  client-side. Methods: ListAsync(filter, ct), GetByIdAsync(id, ct),
  GetUnreadCountAsync(since, ct).
- AnomalyBrowsingState — Singleton holding AnomalyFilter (severity threshold,
  sport set, date range) + LastSeenUtc + cached UnreadCount. OnChange event.

Nav badge:
- NavBody.razor subscribes to AnomalyBrowsingState.OnChange, renders a
  pulsing red m-nav__badge when UnreadCount > 0. Badge resets when the
  user clicks 'Mark all read' on the feed toolbar.

Settings toggle:
- Settings.razor — added Workers:AnomalyDetectionEnabled toggle (backend
  added the flag). Localized via Settings.Worker.AnomalyDetectionEnabled.
- Marathon.UI.Services.WorkerOptions mirror — added AnomalyDetectionEnabled
  (default true).

Localization: +30 RU/EN keys following the dot-segmented convention
(Anomaly.*, Settings.Worker.AnomalyDetectionEnabled). Full key parity verified.

Tests (+31 bUnit, all passing):
- AnomalyFeedTests, AnomalyDetailTests
- AnomalyCardTests, SeverityBadgeTests, AnomalyEvidenceTests
- FakeAnomalyBrowsingService support fake registered in MarathonTestContext.

Routing: deleted the Phase 5 Pages/Anomalies.razor placeholder; new feed
page lives at Pages/Anomalies/AnomalyFeed.razor.

Build: 0 warnings, 0 errors.
Tests: Domain 109 + Application 19 + Infrastructure 80 + UI 68 = 276/276
(baseline 245, +31 new bUnit tests, no regressions).

Phase 7 status:  Done (backend + frontend both complete, awaiting review).

Known deferral: AnomalyBrowsingState.LastSeenUtc is in-memory only; the
unread-count badge resets on app restart. Acceptable for now; Phase 9 may
extend ISettingsWriter or add an ILastSeenStore.
2026-05-05 13:39:39 +03:00

172 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature: Initial Implementation (maraphon-app)
**Branch:** `feature/initial-implementation`
**Base branch:** `main`
**Created:** 2026-05-05
**Status:** 🟡 In Progress
**Strategy:** Big Bang
**Mode:** Automated
**Execution:** Orchestrator
**Implementer models:** Sonnet 4.6 (backend) · Opus (frontend, with frontend-design skill)
**Reviewer model:** Sonnet 4.6
## Summary
Build the maraphon-app end-to-end: a Blazor Hybrid (.NET 8 + WPF) sports-betting odds
analyzer that scrapes marathonbet.by, persists snapshots in SQLite, exports to Excel
matching the customer spec, and detects coefficient-flip anomalies. Architecture is
Clean Architecture with all UI in a Razor Class Library so the host can later swap to
ASP.NET Core Blazor Server with no UI rewrite. RU + EN localization, every variable
parameter configurable.
## Build & Test Commands
- **Build:** `dotnet build Marathon.sln`
- **Test:** `dotnet test Marathon.sln`
- **Lint:** `dotnet format Marathon.sln --verify-no-changes`
- **Run:** `dotnet run --project src/Marathon.Hosts.WpfBlazor`
> **Big Bang strategy:** Build/tests are NOT run for intermediate phases (Phases 08).
> The full build + test suite must pass at Phase 9 before final review.
> An exception: a `dotnet build` *compile-only smoke check* is allowed after each
> phase to catch syntax/type errors early — this is faster than running tests and
> consistent with Big Bang ("we don't run tests until the end").
## Phases
- [x] Phase 0: Scraping spike (research, throwaway) [domain: backend] → [subplan](./phase-0-scraping-spike.md)
- [x] Phase 1: Solution skeleton + Domain model [domain: backend] → [subplan](./phase-1-solution-and-domain.md)
- [x] Phase 2: Infrastructure — Storage [domain: backend] → [subplan](./phase-2-storage.md)
- [x] Phase 3: Infrastructure — Scraping [domain: backend] → [subplan](./phase-3-scraping.md)
- [x] Phase 4: Application layer + Background workers [domain: backend] → [subplan](./phase-4-application-and-workers.md)
- [x] Phase 5: Blazor Hybrid host + Theme + i18n [domain: frontend] → [subplan](./phase-5-host-theme-i18n.md)
- [x] Phase 6: Event browsing UI [domain: frontend] → [subplan](./phase-6-event-browsing-ui.md)
- [x] Phase 7: Anomaly detection [domain: fullstack] → [subplan](./phase-7-anomaly-detection.md)
- [ ] Phase 8: Results loader [domain: fullstack] → [subplan](./phase-8-results-loader.md)
- [ ] Phase 9: Packaging + polish (final phase — full build + tests required) [domain: fullstack] → [subplan](./phase-9-packaging-polish.md)
## Parallelization Plan (Orchestrator mode)
| Round | Phases | Notes |
|---|---|---|
| 1 | Phase 0 | Spike — gating research, no parallelism |
| 2 | Phase 1 | Domain — must finish before Phases 2/3/5 |
| 3 | **Phases 2, 3, 5 in parallel** | Storage, Scraping, UI Shell — disjoint files |
| 4 | Phase 4 | Application + Workers — depends on 2 + 3 |
| 5 | Phase 6 | Event UI — depends on 4 + 5 |
| 6 | Phase 7 | Anomaly detection — depends on 6 |
| 7 | Phase 8 | Results loader — depends on 6 |
| 8 | Phase 9 | Packaging — final, runs full build + tests |
## Phase Progress Log
| Phase | Domain | Status | Review | Build | Committed |
|---|---|---|---|---|---|
| Phase 0: Scraping spike | backend | ✅ Done | ⚠️ Pass with notes (Sonnet) | ⏭️ N/A (research) | ✅ 070e34b |
| Phase 1: Solution + Domain | backend | ✅ Done | ⚠️ Pass with notes (Sonnet) | ✅ Build OK + 96/96 Domain tests | ✅ 61114ea |
| Phase 2: Storage | backend | ✅ Done | ⚠️ Pass with notes (Sonnet, combined batch) | ✅ Build OK + 77/77 Infra tests | ✅ batch (e4d8476…686550d…+) |
| Phase 3: Scraping | backend | ✅ Done | ⚠️ Pass with notes (Sonnet, combined batch) | ✅ Build OK + 77/77 Infra tests | ✅ batch (e4d8476…686550d…+) |
| Phase 4: Application + Workers | backend | ✅ Done | ⚠️ Pass with notes (Sonnet) | ✅ Build OK + 202/202 tests | ✅ 2acbaa5 |
| Phase 5: Host + Theme + i18n | frontend | ✅ Done | ⚠️ Pass with notes (Sonnet, combined batch) | ✅ Build OK + 11/11 UI tests | ✅ batch (e4d8476…686550d…+) |
| Phase 6: Event browsing UI | frontend | ✅ Done | ⚠️ Pass with notes (Sonnet) | ✅ Build OK + 228/228 tests | ✅ 553db2b |
| Phase 7: Anomaly detection | fullstack | ✅ Done | ⬜ | ✅ Build OK + 276/276 tests | ⬜ |
| Phase 8: Results loader | fullstack | ⬜ Not Started | ⬜ | ⏭️ Big Bang | ⬜ |
| Phase 9: Packaging + polish | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
## Final Review
- [ ] Comprehensive code review (final-reviewer agent)
- [ ] Security review (auth N/A, but covers scraping HttpClient, file I/O, user input)
- [ ] Full build passes
- [ ] Full test suite passes
- [ ] User merge approval
- [ ] Merged to `main`
## Resume Notes (2026-05-05 — paused at end of parallel batch P2/P3/P5)
**Where we left off:**
The parallel batch (Phases 2, 3, 5) completed code-wise. Phase 5 was killed near the
end of its "verify build" step. All files are committed as a single WIP snapshot
on `feature/initial-implementation` so nothing is lost. No reviewer ran on this batch
yet, and the solution does NOT build cleanly — there are known cross-phase compile
issues to resolve before review.
**Tomorrow's action list (in order):**
1. `git pull` (or just verify branch) — confirm we're on `feature/initial-implementation`
at the WIP commit.
2. Run `dotnet build Marathon.sln` to capture the current error set as a baseline.
3. **Resolve known cross-phase compile issues:**
- **Phase 2 ↔ Phase 3:** Phase 2's repository classes are `internal`; Phase 3's
`Marathon.Infrastructure.Tests` references them directly. Fix: add
`<InternalsVisibleTo Include="Marathon.Infrastructure.Tests" />` to
`src/Marathon.Infrastructure/Marathon.Infrastructure.csproj`. (Or make the
repos public — choose by reading the actual csproj first.)
- **Phase 5:** `LocalizationOptions` namespace ambiguity (Microsoft.AspNetCore
vs Microsoft.Extensions). Fix in WPF host or UI project — qualify or alias.
- **Phase 5:** Serilog API mismatch in WPF host (likely `UseSerilog` extension
not found because Serilog.Extensions.Hosting wasn't pulled in transitively
via the right namespace, OR the API call site uses an older Serilog API).
4. Once `dotnet build Marathon.sln` is green:
- Run `dotnet test Marathon.sln` to see how many tests pass.
- Spawn the phase-reviewer agent (Sonnet) to review the parallel batch as a
single combined review (Phase 2 + 3 + 5 diff). Pass `git diff 144c936...HEAD`.
- Address blocker findings; re-review until pass.
5. After review passes, finalize with one or more clean commits (the WIP commit
can be `git reset --soft` to base and re-committed cleanly per phase, OR left
as-is and the review passes apply). Update PLAN.md tracking rows for P2/P3/P5
to ✅ Done with commit hashes.
6. Move to **Phase 4** (Application + Workers — backend, Sonnet 4.6). Phase 4
composes the per-module DI extensions (`PersistenceModule.AddMarathonPersistence`
and `ScrapingModule.AddMarathonScraping`) into a top-level
`Marathon.Infrastructure/DependencyInjection.cs` and adds `BackgroundService`
pollers (`UpcomingEventsPoller`, `LiveOddsPoller`, plus a future
`ResultsWatchListPoller` per the Phase 8 amendment).
**Useful pointers:**
- Phase 2 implementer report: see `tasks/a56ecc5e24bd7ea43.output` (don't read —
context-heavy; the summary is in the conversation transcript).
- Phase 3 implementer report: agent ID `a8a537ba5721fba3d`. Same caveat.
- Phase 5 implementer was killed; final state is the WIP commit. The agent had
finished implementation and was about to verify build — assume code is ~95%
complete but unreviewed.
- All 3 phase subplans have their `## Handoff to Next Phase` sections filled.
- Cross-phase issues already documented in the conversation by the parallel
agents — see Phase 2 and Phase 3 reports for the specifics.
**Do NOT:**
- Reset/discard the WIP commit without first reading what's in it.
- Skip the cross-phase fix step — Phase 4 cannot proceed against a broken build.
- Move to Phase 4 before reviewing the P2/P3/P5 batch.
## Amendment Log
### Amendment 1 — 2026-05-05 — Phase 8 strategy change (deferred — formal approval will be requested when Phase 8 begins)
**Type:** Modify upcoming phase (Phase 8 — Results loader)
**What changed:** Phase 8's original subplan assumed marathonbet.by exposes a public
results / archive page that we can scrape to back-fill `EventResult`s. Phase 0 spike
proved this endpoint does NOT exist (`/su/results` returns 404).
**Why:** Spike findings — see `spike/SCRAPE_FINDINGS.md` and the deviation note in
`plans/initial-implementation/phase-0-scraping-spike.md` (Handoff section).
**New approach (to be formalised when Phase 8 begins):** Maintain a "watch list" of
events whose `ScheduledAt + EstimatedDuration` is in the past but whose status is not
`Completed`. Poll those event-detail URLs every 5 min until either
`eventJsonInfo.matchIsComplete=true` (capture `resultDescription`, mark complete) or
the URL 404s (mark `ResultUnknown`). Optional fallback to flashscore/sofascore is a
Phase 8 design decision.
**Impact on existing phases:** Phase 4 (Application + Workers) may need a new
`ResultsWatchListPoller : BackgroundService` in addition to the previously planned
`UpcomingEventsPoller` and `LiveOddsPoller`. Phase 2 schema may need a `WatchStatus`
field on `Event` (`Pending | InWatchList | Completed | ResultUnknown`). Both will be
re-evaluated when Phase 8 starts.
**Status:** Logged — formal subplan revision and user approval will be requested at the
start of Phase 8 (per skill rule: "All amendments require explicit user approval before
taking effect"). Phases 17 do not depend on Phase 8's tactical implementation.