docs(initial-implementation): add feature plan and 10 phase subplans
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# Phase 1: Solution Skeleton + Domain Model
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
|
||||
Create the .NET 8 solution structure (5 source projects + 4 test projects) and implement
|
||||
the core domain model — entities, value objects, enums, and invariants — with no
|
||||
external dependencies. This establishes the foundation that all later phases reference.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Create `Marathon.sln` with these projects:
|
||||
- `src/Marathon.Domain/Marathon.Domain.csproj` (classlib, .NET 8, no deps)
|
||||
- `src/Marathon.Application/Marathon.Application.csproj` (classlib, refs Domain)
|
||||
- `src/Marathon.Infrastructure/Marathon.Infrastructure.csproj` (classlib, refs Domain + Application)
|
||||
- `src/Marathon.UI/Marathon.UI.csproj` (Razor Class Library, refs Domain + Application)
|
||||
- `src/Marathon.Hosts.WpfBlazor/Marathon.Hosts.WpfBlazor.csproj` (WPF + BlazorWebView,
|
||||
refs Marathon.UI + Marathon.Infrastructure + Marathon.Application)
|
||||
- `tests/Marathon.Domain.Tests/Marathon.Domain.Tests.csproj` (xUnit)
|
||||
- `tests/Marathon.Application.Tests/Marathon.Application.Tests.csproj` (xUnit)
|
||||
- `tests/Marathon.Infrastructure.Tests/Marathon.Infrastructure.Tests.csproj` (xUnit)
|
||||
- `tests/Marathon.UI.Tests/Marathon.UI.Tests.csproj` (bUnit + xUnit)
|
||||
- [ ] Add `Directory.Build.props` at repo root with shared settings:
|
||||
```xml
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>12</LangVersion>
|
||||
<TreatWarningsAsErrors Condition="'$(Configuration)'=='Release'">true</TreatWarningsAsErrors>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
```
|
||||
- [ ] Add `Directory.Packages.props` for centralized NuGet versions (mark
|
||||
`<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>`).
|
||||
- [ ] Add `.editorconfig` at repo root with C# formatting rules consistent with
|
||||
CLAUDE.md conventions (file-scoped namespaces, 4-space indent, etc.).
|
||||
- [ ] Implement `Marathon.Domain` types:
|
||||
- **Value objects (records):**
|
||||
- `SportCode(int Value)` — must be > 0
|
||||
- `EventId(string Value)` — bookmaker's event identifier (string, not int)
|
||||
- `Side` enum: `Side1, Side2, Draw, Less, More`
|
||||
- `BetScope` discriminated union: `Match | Period(int Number)` (use record hierarchy)
|
||||
- `BetType` enum: `Win, Draw, WinFora, Total`
|
||||
- `OddsRate(decimal Value)` — must be > 1.0
|
||||
- `OddsValue(decimal Value)` — handicap or total threshold (e.g., -5.5, 220.5)
|
||||
- **Entities (use records or classes with private setters as appropriate):**
|
||||
- `Sport(SportCode Code, string NameRu, string NameEn)`
|
||||
- `Country(string Code, string NameRu, string NameEn)`
|
||||
- `League(string Id, SportCode Sport, string Country, string NameRu, string NameEn,
|
||||
string Category)`
|
||||
- `Event(EventId Id, SportCode Sport, string CountryCode, string LeagueId,
|
||||
string Category, DateTimeOffset ScheduledAt, string Side1Name, string Side2Name)`
|
||||
- `Bet(BetScope Scope, BetType Type, Side Side, OddsValue? Value, OddsRate Rate)`
|
||||
- `OddsSnapshot(EventId EventId, DateTimeOffset CapturedAt, OddsSource Source,
|
||||
IReadOnlyList<Bet> Bets)` where `OddsSource = PreMatch | Live`
|
||||
- `EventResult(EventId EventId, int Side1Score, int Side2Score, Side WinnerSide,
|
||||
DateTimeOffset CompletedAt)`
|
||||
- `Anomaly(Guid Id, EventId EventId, DateTimeOffset DetectedAt, AnomalyKind Kind,
|
||||
decimal Score, string EvidenceJson)` where `AnomalyKind = SuspensionFlip`
|
||||
- [ ] Implement domain invariants in record constructors / static factory methods.
|
||||
- [ ] Implement `Marathon.Domain.Tests` — TDD tests for invariants:
|
||||
- `OddsRate` rejects ≤ 1.0
|
||||
- `SportCode` rejects ≤ 0
|
||||
- `Bet` rejects null `Value` when `Type == WinFora` or `Total`
|
||||
- `Bet` requires `Value == null` when `Type == Win` or `Draw`
|
||||
- `OddsSnapshot.Bets` is non-empty
|
||||
- `Event.ScheduledAt` is UTC (`Offset == TimeSpan.Zero`)
|
||||
- Domain types are immutable (no settable public properties)
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `Marathon.sln`
|
||||
- `Directory.Build.props`
|
||||
- `Directory.Packages.props`
|
||||
- `.editorconfig`
|
||||
- `src/Marathon.Domain/**` — entities, VOs, enums, invariants
|
||||
- `src/Marathon.Application/Marathon.Application.csproj` — empty stub csproj
|
||||
- `src/Marathon.Infrastructure/Marathon.Infrastructure.csproj` — empty stub
|
||||
- `src/Marathon.UI/Marathon.UI.csproj` — empty RCL stub
|
||||
- `src/Marathon.Hosts.WpfBlazor/Marathon.Hosts.WpfBlazor.csproj` — empty stub
|
||||
- `tests/Marathon.Domain.Tests/**` — invariant tests
|
||||
- `tests/Marathon.{Application,Infrastructure,UI}.Tests/*.csproj` — empty xUnit stubs
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `dotnet build Marathon.sln` succeeds (compile-only smoke check, allowed in Big Bang).
|
||||
- All domain tests pass (`dotnet test tests/Marathon.Domain.Tests` is allowed even in
|
||||
Big Bang since this is the foundation phase and the test project is self-contained).
|
||||
- Domain types are public, immutable records with invariants enforced in constructors.
|
||||
- No EF Core, scraping, or UI code in this phase.
|
||||
|
||||
## Notes
|
||||
|
||||
- Use file-scoped namespaces and one type per file (except small enum + record groups).
|
||||
- Domain types must NOT reference `System.Net.Http`, EF Core, or any infrastructure.
|
||||
- For the discriminated union `BetScope`, use a record hierarchy:
|
||||
```csharp
|
||||
public abstract record BetScope { /* private ctor */ }
|
||||
public sealed record MatchScope : BetScope;
|
||||
public sealed record PeriodScope(int Number) : BetScope;
|
||||
```
|
||||
Or a single record with a nullable `PeriodNumber` — implementer's choice, document it.
|
||||
- Test framework: xUnit with FluentAssertions. Don't add Mockito/NSubstitute yet
|
||||
(no abstractions to mock in Domain).
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] Solution builds (`dotnet build`)
|
||||
- [ ] Domain tests all pass
|
||||
- [ ] No external deps in `Marathon.Domain.csproj` except framework packages
|
||||
- [ ] Public API surface is minimal — only what later phases need
|
||||
- [ ] All types follow CLAUDE.md naming/style conventions
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Filled by Phase 1 implementer. -->
|
||||
Reference in New Issue
Block a user