Files
maraphon-app/CLAUDE.md
T

4.2 KiB

CLAUDE.md — maraphon-app

Project memory for Claude Code sessions on this repository. Keep entries concise. Per-feature learnings are appended below by the feature-planner workflow.

Project Overview

maraphon-app is a sports betting odds analyzer for marathonbet.by. It scrapes pre-match (/su) and live (/su/live) events, persists odds snapshots over time, and detects anomalies — especially the odds-flip pattern (bookmaker freezes bets then inverts underdog/favorite coefficients).

Architecture (Clean Architecture, 5 projects + tests)

Marathon.Domain          ← entities, value objects, no external deps
   ↑
Marathon.Application     ← use cases + abstractions (IOddsScraper, IRepository, ...)
   ↑
Marathon.Infrastructure  ← EF Core (SQLite), scraping (AngleSharp/Playwright), Excel, Polly
Marathon.UI              ← Razor Class Library (all Blazor components — host-agnostic)
   ↑
Marathon.Hosts.WpfBlazor ← WPF + BlazorWebView host (replaceable for ASP.NET Core later)

Key portability invariant: All UI lives in Marathon.UI (Razor Class Library). The host project (Marathon.Hosts.WpfBlazor) is the only thing that changes when migrating to a web app — drop in an ASP.NET Core Blazor Server host that references the same RCL.

Tech stack

  • .NET 8 LTS, C# 12
  • EF Core 8 + SQLite (WAL mode)
  • AngleSharp (HTML), Playwright for .NET (SPA fallback)
  • Polly v8 (Microsoft.Extensions.Http.Resilience)
  • MudBlazor components, Plotly.Blazor charts
  • Serilog logging (rolling file + console)
  • xUnit + FluentAssertions + NSubstitute, in-memory SQLite for repo tests

Build & test

Command Purpose
dotnet build Marathon.sln Build all projects
dotnet test Marathon.sln Run all tests
dotnet format Marathon.sln --verify-no-changes Lint
dotnet run --project src/Marathon.Hosts.WpfBlazor Run desktop app

Coding conventions

  • Nullable reference types: enabled (<Nullable>enable</Nullable>)
  • Implicit usings: enabled
  • Treat warnings as errors in Release builds
  • File-scoped namespaces
  • One public type per file (except small DTOs/records grouped in a feature folder)
  • Domain entities: prefer record for immutable data; class with private setters when identity matters
  • No mutation of domain objects after construction — return new instances
  • Repositories return IReadOnlyList<T>, not List<T> or IEnumerable<T> (clarity on enumeration cost)
  • Tests follow Given_When_Then or Should_<expected>_When_<condition> naming

Configuration

Every variable parameter is configurable via appsettings.json and overridable via appsettings.Local.json (gitignored) or environment variables:

  • Scraping:PollingIntervalSeconds (default 30)
  • Scraping:MaxConcurrentRequests (default 4)
  • Scraping:UserAgents[] (rotated per request)
  • Scraping:RetryPolicy:* (Polly settings)
  • Scraping:RateLimit:RequestsPerSecond (default 1)
  • Storage:DatabasePath (default ./data/marathon.db)
  • Storage:ExportDirectory (default ./exports)
  • Storage:SnapshotRetentionDays (default 90)
  • Anomaly:SuspensionGapSeconds (default 60)
  • Anomaly:OddsFlipThreshold (default 0.30 — implied probability delta)
  • Localization:DefaultCulture (default ru-RU)

A future Settings page in the UI binds to these.

Domain model summary

  • Sport(Code, Name) — e.g., (6, "Баскетбол")
  • Event(Id, SportCode, CountryCode, LeagueId, CategoryId, ScheduledAt, EventCodeFromBookmaker)
  • OddsSnapshot(EventId, CapturedAt, Source: Pre|Live, Bets: List<Bet>)
  • Bet(Scope: Match|Period[N], Type: Win|Draw|WinFora|Total, Side: 1|2|Less|More, Value?, Rate)
  • EventResult(EventId, FinalScore, WinnerSide)
  • Anomaly(EventId, DetectedAt, Kind: SuspensionFlip, Score, EvidenceTimeline)

Excel export schema (compliance with customer spec)

Customer TZ requires wide-table layout with columns like Bet_Match_Win_1, Bet_Period-1_Win_Fora_2_Value, etc.

Internal storage is normalized (one row per Bet in OddsSnapshots). The Excel exporter denormalizes to the wide format on demand. Filename pattern:

Marathon_<YYYY-MM-DD>_to_<YYYY-MM-DD>.xlsx

Recurring Issues & Patterns

(Populated as we work — leave empty until something repeats.)