5.8 KiB
5.8 KiB
Phase 2: Infrastructure — Storage
Status: ⬜ Not Started Parent plan: PLAN.md Domain: backend
Objective
Implement persistent storage: EF Core + SQLite (WAL) with migrations, repository implementations of the Application layer's interfaces, and a ClosedXML-based Excel exporter that produces files matching the customer's wide-column spec with date-range filenames.
Tasks
- Add packages to
Marathon.Infrastructure(viaDirectory.Packages.props):Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.SqliteMicrosoft.EntityFrameworkCore.DesignClosedXML
- Add Application-layer abstractions in
Marathon.Application/Abstractions/:IRepository<TKey, TEntity>— generic CRUD:GetAsync,ListAsync,AddAsync,UpdateAsync,DeleteAsync,SaveChangesAsyncIEventRepository : IRepository<EventId, Event>— addsListByDateRangeAsync,ListBySportAsyncISnapshotRepository : IRepository<Guid, OddsSnapshot>— addsListByEventAsync(EventId, DateTimeOffset from, DateTimeOffset to)IResultRepository : IRepository<EventId, EventResult>IAnomalyRepository : IRepository<Guid, Anomaly>IExcelExporter—ExportAsync(DateRange range, ExportKind kind, string outputPath)whereExportKind = PreMatch | Live | Combined
- Implement
MarathonDbContextinMarathon.Infrastructure/Persistence/:DbSet<EventEntity>,DbSet<SnapshotEntity>,DbSet<BetEntity>,DbSet<EventResultEntity>,DbSet<AnomalyEntity>,DbSet<SportEntity>,DbSet<LeagueEntity>- Configure SQLite with WAL via connection string
- Use
EntityTypeConfiguration<T>classes (one per entity inConfigurations/) - Map domain types ↔ EF entities via mapping helpers (don't pollute domain)
- Indexes:
(EventId)onSnapshotsandBets;(Sport, ScheduledAt)onEvents
- Implement
Migrations/InitialCreatemigration (EF Core CLI):dotnet ef migrations add InitialCreate --project src/Marathon.Infrastructure - Implement repositories in
Marathon.Infrastructure/Persistence/Repositories/:EventRepository,SnapshotRepository,ResultRepository,AnomalyRepository- Each maps EF entity ↔ domain type at the boundary
- Implement
ExcelExporterinMarathon.Infrastructure/Export/:- Uses ClosedXML
- Output filename:
Marathon_<from yyyy-MM-dd>_to_<to yyyy-MM-dd>.xlsx - Two sheets:
PreMatchandLive(or only the selected one based onExportKind) - Wide columns matching customer spec exactly:
- Event metadata:
RowNum,SportCode,Sport,Country,League,Category,DateFull,Day,Month,Year,Time,EventId - Match-level bets:
Bet_Match_Win_1,Bet_Match_Draw,Bet_Match_Win_2,Bet_Match_Win_Fora_1_Value,Bet_Match_Win_Fora_1_Rate, etc. - Period-N bets: dynamically generated for max periods seen (
Bet_Period-1_Win_1, ...) - For Live export, prefix with
Live_instead ofBet_ - Final column:
WinnerSide(1 or 2 based on lowest pre-match Win rate, per spec §1.2.4 / §2.2.4)
- Event metadata:
- Implement a
BetRowDenormalizerhelper that takes aList<Bet>and produces a flatDictionary<string, object?>keyed by spec column names.
- Add a DI extension
AddMarathonInfrastructure(IServiceCollection, IConfiguration)inMarathon.Infrastructure/DependencyInjection.csthat wires up DbContext + repositories + exporter usingIConfigurationforStorage:DatabasePathandStorage:ExportDirectory. - Tests in
Marathon.Infrastructure.Tests:- In-memory SQLite (
Microsoft.Data.SqlitewithMode=Memory;Cache=Shared) - Test: insert + retrieve
Event,OddsSnapshot,Anomalyround-trip preserves all domain fields - Test:
ExcelExportergenerates a workbook with the expected sheet names, headers matching spec, and row count matching event count - Test: filename pattern matches
Marathon_yyyy-MM-dd_to_yyyy-MM-dd.xlsx - Test: WAL mode is enabled after open
- In-memory SQLite (
Files to Modify/Create
src/Marathon.Application/Abstractions/I*.cs— repository interfacessrc/Marathon.Application/ExportKind.cs,DateRange.cssrc/Marathon.Infrastructure/Persistence/MarathonDbContext.cssrc/Marathon.Infrastructure/Persistence/Entities/*.cssrc/Marathon.Infrastructure/Persistence/Configurations/*Configuration.cssrc/Marathon.Infrastructure/Persistence/Repositories/*Repository.cssrc/Marathon.Infrastructure/Persistence/Mapping.cs— entity ↔ domainsrc/Marathon.Infrastructure/Export/ExcelExporter.cssrc/Marathon.Infrastructure/Export/BetRowDenormalizer.cssrc/Marathon.Infrastructure/Migrations/*— EF migrationssrc/Marathon.Infrastructure/DependencyInjection.cstests/Marathon.Infrastructure.Tests/**
Acceptance Criteria
- All Infrastructure code compiles (Big Bang: compile-only smoke check OK).
- DbContext + repositories cover all domain types.
- Excel exporter output matches customer spec column names exactly (no typos in
Bet_Match_Win_Fora_1_Value, hyphens inPeriod-1, etc.). - Filename includes inclusive date range from event scheduling.
Notes
- This phase is parallelizable with Phase 3 (Scraping) — they touch disjoint files.
ExcelExporteruses normalized DB data and produces wide columns — DO NOT store data in wide format in SQLite.- Big Bang: do NOT run full test suite. A
dotnet buildsmoke check is acceptable.
Review Checklist
- Solution builds (compile-only)
- Excel column names match customer spec exactly (cross-check against TZ §1.2 / §2.2)
- Filename pattern matches
Marathon_yyyy-MM-dd_to_yyyy-MM-dd.xlsx - No domain types polluted with EF attributes — mapping is in
Configurations/ - WAL mode enabled in connection string