chore(med): mapping culture-safe parse, dead-code, scope comparer, UA rotator, parser cache
Six MEDIUM-tier review items:
* Mapping.cs — DateTimeOffset.Parse now passes CultureInfo.InvariantCulture
+ DateTimeStyles.RoundtripKind so a non-en-US thread culture cannot
corrupt round-tripped ScheduledAt / CapturedAt / DetectedAt / CompletedAt.
Also replaces the magic 0/1 BetScope discriminator with named constants.
* Delete dead Placeholder.cs files in Marathon.Application and
Marathon.Infrastructure — they were stubs from Phase 1 to satisfy
"non-empty project" and have been dead since Phase 2/3.
* EventBrowsingService — drop the bespoke ScopeEqualityComparer; BetScope
is a record hierarchy, .GroupBy uses value equality natively.
* UserAgentRotatorHandler — counter promoted to private static int with
Interlocked.Increment so rotation is round-robin across the process.
HttpClientFactory builds the handler Transient, so the previous instance
field reset to zero on every new client and broke rotation.
* EventOddsParser — added a parallel "selection-key → IElement" index
alongside the existing price index. Handicap extraction (6 call sites
per event detail page) used to do a fresh document.QuerySelector("span[
data-selection-key='...']") for every key — full-document CSS traversal.
Now it's a dictionary lookup, with the pair-emit logic factored into a
shared TryEmitHandicapPair helper.
This commit is contained in:
@@ -204,8 +204,10 @@ public sealed class EventBrowsingService : IEventBrowsingService
|
||||
private static IReadOnlyList<EventScopeBoard> BuildBoards(OddsSnapshot snapshot)
|
||||
{
|
||||
// Group by scope, preserve Match-first order then ascending Period numbers.
|
||||
// BetScope is a record hierarchy so .GroupBy uses value equality natively —
|
||||
// no custom comparer needed.
|
||||
var groups = snapshot.Bets
|
||||
.GroupBy(static b => b.Scope, ScopeEqualityComparer.Instance)
|
||||
.GroupBy(static b => b.Scope)
|
||||
.OrderBy(static g => OrderKey(g.Key));
|
||||
|
||||
var boards = new List<EventScopeBoard>();
|
||||
@@ -241,23 +243,4 @@ public sealed class EventBrowsingService : IEventBrowsingService
|
||||
PeriodScope p => p.Number,
|
||||
_ => int.MaxValue,
|
||||
};
|
||||
|
||||
private sealed class ScopeEqualityComparer : IEqualityComparer<BetScope>
|
||||
{
|
||||
public static readonly ScopeEqualityComparer Instance = new();
|
||||
public bool Equals(BetScope? x, BetScope? y) => (x, y) switch
|
||||
{
|
||||
(null, null) => true,
|
||||
(MatchScope, MatchScope) => true,
|
||||
(PeriodScope a, PeriodScope b) => a.Number == b.Number,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public int GetHashCode(BetScope obj) => obj switch
|
||||
{
|
||||
MatchScope => 0,
|
||||
PeriodScope p => p.Number,
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user