Files
maraphon-app/plans/initial-implementation/phase-7-anomaly-detection.md
T

108 lines
4.8 KiB
Markdown

# Phase 7: Anomaly Detection (Suspension + Flip)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
**Implementer:** Sonnet (backend portion) + Opus (UI portion, with frontend-design)
**Depends on:** Phase 4 (snapshot pipeline) + Phase 6 (UI patterns)
## Objective
Detect the **odds-flip anomaly** described in customer TZ §3: bookmaker freezes betting
on a live event, then re-opens with inverted underdog/favorite odds. Persist anomalies
and surface them in a dedicated UI feed page so the user can act on them.
## Tasks
### Backend (Sonnet)
- [ ] Implement `Marathon.Domain/AnomalyDetection/AnomalyDetector.cs`:
- Pure domain logic — takes `IReadOnlyList<OddsSnapshot>` for an event, returns
`IReadOnlyList<Anomaly>`
- Detect suspension intervals: gaps between snapshots > `SuspensionGapSeconds`
(configurable)
- For each suspension, compute pre-suspension and post-suspension implied
probability vectors `(p1, pDraw, p2)` from Win-1/Draw/Win-2 rates
- Compute flip score: `max(|p_post[i] - p_pre[i]|)` across i ∈ {1, draw, 2}
- If flip score ≥ `OddsFlipThreshold` AND the favorite changed (argmax differs),
emit an `Anomaly(Kind=SuspensionFlip, Score, EvidenceJson)` where `EvidenceJson`
contains the snapshots bracketing the suspension
- [ ] Add `AnomalyOptions` POCO bound to `Anomaly:*`:
```csharp
public sealed class AnomalyOptions {
public int SuspensionGapSeconds { get; init; } = 60;
public decimal OddsFlipThreshold { get; init; } = 0.30m;
public int MinSnapshotCount { get; init; } = 3;
}
```
- [ ] Implement `DetectAnomaliesUseCase` in `Marathon.Application/UseCases/`:
- Iterate over events with new snapshots since last detection run
- Invoke `AnomalyDetector` per event
- Persist new anomalies via `IAnomalyRepository`
- [ ] Implement `AnomalyDetectionPoller : BackgroundService` in
`Marathon.Infrastructure/Workers/`:
- Runs every `Anomaly:DetectionIntervalSeconds` (default 60s)
- Calls `DetectAnomaliesUseCase`
- [ ] Backend tests in `Marathon.Domain.Tests/AnomalyDetection/`:
- Synthetic snapshot timeline with no flip → 0 anomalies
- Snapshot timeline with suspension + small odds shift → 0 anomalies (below threshold)
- Snapshot timeline with suspension + large flip (favorite ↔ underdog) → 1 anomaly
- Score calculation matches expected value
### Frontend (Opus + frontend-design)
- [ ] Create `Marathon.UI/Pages/Anomalies/AnomalyFeed.razor`:
- List of anomalies sorted by `DetectedAt` descending
- Each card shows: severity (color-coded by score), event identity, sport icon,
detected timestamp, mini sparkline of pre/post odds
- Click card → expand to show evidence timeline (snapshots before/after suspension)
- Filter: severity threshold, sport, date range
- [ ] Create `Marathon.UI/Components/AnomalyCard.razor` — visually distinctive,
attention-grabbing without being garish; follows frontend-design guidance for
information hierarchy.
- [ ] Add navigation entry to `MainLayout` drawer with notification badge showing
unread anomaly count.
- [ ] Localize all strings in RU + EN.
- [ ] Frontend tests in `Marathon.UI.Tests/Pages/Anomalies/`:
- bUnit: anomaly card renders evidence timeline
- bUnit: filter narrows the list correctly
## Files to Modify/Create
- `src/Marathon.Domain/AnomalyDetection/AnomalyDetector.cs`
- `src/Marathon.Domain/AnomalyDetection/SuspensionInterval.cs`
- `src/Marathon.Application/UseCases/DetectAnomaliesUseCase.cs`
- `src/Marathon.Application/Configuration/AnomalyOptions.cs` (or in Infra)
- `src/Marathon.Infrastructure/Workers/AnomalyDetectionPoller.cs`
- `src/Marathon.UI/Pages/Anomalies/AnomalyFeed.razor`
- `src/Marathon.UI/Components/AnomalyCard.razor`
- `tests/Marathon.Domain.Tests/AnomalyDetection/AnomalyDetectorTests.cs`
- `tests/Marathon.UI.Tests/Pages/Anomalies/**`
## Acceptance Criteria
- Compiles (Big Bang).
- `AnomalyDetector` is a pure function — no I/O, no DI dependencies.
- Configurable thresholds via `appsettings.json` (visible in Settings page).
- UI clearly distinguishes high/medium/low severity anomalies.
- Evidence timeline shows the actual snapshots that triggered the detection.
## Notes
- This is the **product's actual differentiator** — quality of detection logic and
evidence presentation matters. Spend time getting the score formula right.
- Implied probability formula: `p = 1 / odds` (then normalize so they sum to 1).
- Big Bang: compile-only smoke check.
## Review Checklist
- [ ] Detector is deterministic and pure
- [ ] Score calculation correct (verify against hand-computed example)
- [ ] No false positives on synthetic "normal" timelines
- [ ] UI evidence timeline matches stored `EvidenceJson`
- [ ] All strings localized
## Handoff to Next Phase
<!-- Filled by Phase 7 implementer. -->