docs: capture H-series learnings in project memory
Recurring Issues & Patterns: EF migration generation via dotnet ef + the migrations-remove snapshot hazard, Sports.Code ValueGeneratedNever, validated get-only record props blocking `with` (CS0200), and the JsonSettingsWriter section-replace secret-clobbering gotcha + UI-mirror-options pattern. New "Analysis Hardening (H-series)" learnings: the 4-detector fan-out + IsDirectional() gate, SavedStrategy NOCASE name collation, and the config-gated paper-trading worker.
This commit is contained in:
@@ -121,6 +121,26 @@ Marathon_<YYYY-MM-DD>_to_<YYYY-MM-DD>.xlsx
|
||||
- **`Event.ScheduledAt` requires offset `+03:00`.** Test fixtures and any code
|
||||
that constructs Moscow datetimes must use `new DateTimeOffset(date, TimeSpan.FromHours(3))`,
|
||||
never pass a `DateTime.UtcNow` value to that constructor.
|
||||
- **EF migrations — generate with `dotnet ef`, do NOT `migrations remove`.**
|
||||
`MarathonDbContextFactory` is a self-contained design-time factory, so
|
||||
`dotnet ef migrations add X --project src/Marathon.Infrastructure --startup-project src/Marathon.Infrastructure`
|
||||
works. AVOID `dotnet ef migrations remove`: older migrations were hand-written without a
|
||||
Designer snapshot, so remove blanks `MarathonDbContextModelSnapshot.cs` and the next `add`
|
||||
regenerates the WHOLE schema. Validate a migration on a throwaway DB with
|
||||
`dotnet ef database update --connection "Data Source=<ABSOLUTE>/data/_migtest.db"` (absolute
|
||||
path — EF resolves relatives from the build-output dir, not the shell cwd; create `./data/` first).
|
||||
- **`Sports.Code` is `.ValueGeneratedNever()`** — it's the bookmaker's natural sport id
|
||||
(6/11/22723…), not an autoincrement surrogate. Without it EF's int-PK convention emits a
|
||||
spurious AUTOINCREMENT `AlterColumn` on every migration.
|
||||
- **Validated get-only record properties block `with`** (CS0200): `BacktestStrategy.MinScore`,
|
||||
`PaperBet.Rate`/`Stake` are re-declared with validation, so build a new instance instead of
|
||||
`with { ThatProp = … }` (you can still `with` the un-redeclared props, e.g. `SavedStrategy with { Strategy = … }`).
|
||||
- **`JsonSettingsWriter.SaveSectionAsync` replaces the whole section** (`root[section]=json`),
|
||||
so a Settings-UI save drops any key not on the form. Never surface a secret-bearing section
|
||||
(e.g. `Notifications` → Telegram token) in the UI — it would wipe the secret from
|
||||
`appsettings.Local.json`. To surface a non-secret section, add a mutable mirror options class
|
||||
in `Marathon.UI.Services` bound to the same section name (UI can't reference Infrastructure's
|
||||
options types — same pattern as the two `WorkerOptions`).
|
||||
|
||||
## Feature: Initial Implementation > Phase 4: Application + Workers — Learnings
|
||||
|
||||
@@ -203,3 +223,25 @@ For full detail see `spike/SCRAPE_FINDINGS.md` and `spike/SCHEMA_DRAFT.md`.)
|
||||
- **Signal-red is the load-bearing alert tone** for Phase 7. Use
|
||||
`var(--m-c-anomaly)` exclusively (never raw `#dc2626`). Pulsing animation
|
||||
(`m-pulse`) MUST respect `prefers-reduced-motion`.
|
||||
|
||||
## Feature: Analysis Hardening (H-series) — Learnings
|
||||
|
||||
- **Anomaly detectors are a fan-out array in `DetectAnomaliesUseCase`** — 4 now
|
||||
(`SuspensionFlip`, `SteamMove`, `SuspensionFreeze`, `OverroundCompression`). A new detector
|
||||
implements `IAnomalyDetector`, reuses `MatchWinEvidence` for the canonical evidence JSON
|
||||
(so the parser + outcome evaluator work unchanged), and is added to that array. Continuous
|
||||
sliding-window detectors (steam, overround) emit at every `end` and skip suspension-sized
|
||||
gaps so they never overlap the across-suspension flip/freeze detectors.
|
||||
- **`AnomalyKind.IsDirectional()` gates staking/grading** — flip + steam are directional
|
||||
(predict a side); freeze + overround are informational and are excluded from the backtest
|
||||
(`RunBacktestUseCase`) and the outcome evaluator so they don't skew hit-rate/score calibration.
|
||||
- **`SavedStrategy` (backtest presets) use NOCASE name collation** — the unique index AND
|
||||
`GetByNameAsync` both fold case (column-level `UseCollation("NOCASE")`), so save-by-name
|
||||
upserts rather than creating "Kelly"/"kelly" duplicates. Domain stores stake fractions; the
|
||||
form/VM speak percent — convert ×100/÷100 at the UI boundary.
|
||||
- **Paper-trading (forward-test) is a config-gated worker** (`PaperTrading:Enabled`, default
|
||||
false; tunable on the Settings page). `PaperTradingWorker` opens flat-stake `PaperBet`s on
|
||||
new directional anomalies (unique on `AnomalyId`; baseline since-marker advances only after a
|
||||
successful open pass; settle runs in its own catch so a settle failure can't strand the
|
||||
marker) and settles them against results (Won iff pick == winner). `/paper-trading` shows
|
||||
settled-only P&L. The ledger is FK-free (survives snapshot-retention pruning), like `PlacedBets`.
|
||||
|
||||
Reference in New Issue
Block a user