perf: batch repository reads, index snapshots, centralize date encoding
- Add IEventRepository/IResultRepository.GetManyAsync to kill N+1 lookups at 6 sites (backtest, outcome eval, both bet-journal paths, anomaly browsing, results selection); guarded by a Received(1).GetManyAsync test. - Add EventRepository.QueryAsync to push date+sport filtering to SQL (was load-whole-range-then-filter); search/sort stay in-memory for Cyrillic order. - Add AnomalyRepository.CountSinceAsync (unread badge) + ListByDateRangeAsync (feed date filter); add Event/Snapshot count methods for the dashboard. - Add composite indexes IX_Snapshots_EventCode_CapturedAt and _EventCode_Source_CapturedAt via a new migration + model snapshot. - Introduce SqliteDateText as the single source of the O-format date encoding shared by Mapping (read/write) and the repositories' range predicates. - Fix LiveOddsPoller cadence drift (budget sleep against cycle time); make DetectAnomalies dedup O(1) per event; add Event.Title to dedup the title join. Tests adapted to the batched GetManyAsync via a TestFixtures bridge.
This commit is contained in:
@@ -63,29 +63,16 @@ public sealed class RunBacktestUseCase
|
||||
return BacktestSimulator.Run(strategy, Array.Empty<BacktestCandidate>());
|
||||
}
|
||||
|
||||
// Distinct event lookups — minimises repo calls.
|
||||
// TODO (perf, future): batch via IEventRepository.GetManyAsync /
|
||||
// IResultRepository.GetManyAsync once those exist — currently shared
|
||||
// with EvaluateAnomalyOutcomesUseCase, acceptable at expected volumes.
|
||||
// Batched lookups — a single query each, replacing the prior per-event
|
||||
// GetAsync round-trip (N+1 against SQLite).
|
||||
var distinctEventIds = anomalies.Select(a => a.EventId).Distinct().ToList();
|
||||
|
||||
var eventLookup = new Dictionary<DomainEventId, Event>(distinctEventIds.Count);
|
||||
var resultLookup = new Dictionary<DomainEventId, EventResult>(distinctEventIds.Count);
|
||||
var titles = new Dictionary<DomainEventId, string>(distinctEventIds.Count);
|
||||
foreach (var id in distinctEventIds)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var eventLookup = await _events.GetManyAsync(distinctEventIds, ct).ConfigureAwait(false);
|
||||
var resultLookup = await _results.GetManyAsync(distinctEventIds, ct).ConfigureAwait(false);
|
||||
|
||||
var ev = await _events.GetAsync(id, ct).ConfigureAwait(false);
|
||||
if (ev is not null)
|
||||
{
|
||||
eventLookup[id] = ev;
|
||||
titles[id] = string.Concat(ev.Side1Name, " vs ", ev.Side2Name);
|
||||
}
|
||||
|
||||
var res = await _results.GetAsync(id, ct).ConfigureAwait(false);
|
||||
if (res is not null) resultLookup[id] = res;
|
||||
}
|
||||
var titles = new Dictionary<DomainEventId, string>(eventLookup.Count);
|
||||
foreach (var (id, ev) in eventLookup)
|
||||
titles[id] = ev.Title;
|
||||
|
||||
var candidates = new List<BacktestCandidate>(anomalies.Count);
|
||||
foreach (var anomaly in anomalies)
|
||||
|
||||
Reference in New Issue
Block a user