Skip to content

Commit af8fc12

Browse files
author
Andrii Bondarchuk
committed
Refactor race results aggregation and ITT handling
- Change GetRacesResults to return a dictionary keyed by event/subgroup, not a flat list. - Use HashSet for riders and URLs for efficiency and uniqueness. - Update ZwiftFRRTourStageResultsRequest.Urls to HashSet<string>. - Add RulesSet and IsITT properties to ZwiftEventResponse for ITT detection. - Compute and include EGap (elapsed gap) for each rider. - Only include subgroups and riders with results; order by EGap. - Add [JsonIgnore] for IsITT and minor code improvements.
1 parent 4c5da91 commit af8fc12

File tree

2 files changed

+68
-22
lines changed

2 files changed

+68
-22
lines changed

src/FitSyncHub.Functions/Functions/ZwiftFRRTourStageResultsHttpTriggerFunction.cs

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,44 @@ public async Task<IActionResult> Run(
5151
riders.AddRange(ridersPortion);
5252
}
5353

54-
var ridersTaskResult = await GetRacesResults(riders, request.Urls, cancellationToken);
54+
var racesResults = await GetRacesResults([.. riders], request.Urls, cancellationToken);
5555

56-
var result = ridersTaskResult
57-
.Select(x => new
56+
var isITT = racesResults.All(x => x.Key.Event.IsITT);
57+
var bestTimeOverAll = racesResults
58+
.SelectMany(x => x.Value)
59+
.Min(x => x.ActivityData.DurationInMilliseconds);
60+
61+
var result = racesResults
62+
.Where(kv => kv.Value.Count > 0)
63+
.SelectMany(kv =>
5864
{
59-
RiderId = x.ProfileId,
60-
FullName = $"{x.ProfileData.FirstName} {x.ProfileData.LastName}",
61-
Duration = TimeSpan.FromMilliseconds(x.ActivityData.DurationInMilliseconds),
62-
CritivalPower = x.CriticalP,
63-
AvgPower = x.SensorData.AvgWatts,
64-
AvgPowerPerKg = x.SensorData.AvgWatts / (x.ProfileData.WeightInGrams / 1000.0),
65+
var raceResults = kv.Value;
66+
67+
var bestTime = isITT
68+
? bestTimeOverAll
69+
: raceResults.Min(x => x.ActivityData.DurationInMilliseconds);
70+
71+
var bestTimeSpan = TimeSpan.FromMilliseconds(bestTime);
72+
73+
return raceResults.Select(x =>
74+
{
75+
var duration = TimeSpan.FromMilliseconds(x.ActivityData.DurationInMilliseconds);
76+
77+
return new
78+
{
79+
RiderId = x.ProfileId,
80+
FullName = $"{x.ProfileData.FirstName} {x.ProfileData.LastName}",
81+
Duration = duration,
82+
CritivalPower = x.CriticalP,
83+
AvgPower = x.SensorData.AvgWatts,
84+
AvgPowerPerKg = x.SensorData.AvgWatts / (x.ProfileData.WeightInGrams / 1000.0),
85+
EGap = duration - bestTimeSpan,
86+
};
87+
});
6588
})
66-
.OrderBy(x => x.Duration)
89+
.OrderBy(x => x.EGap)
6790
.ToList();
6891

69-
7092
return new OkObjectResult(result);
7193
}
7294

@@ -84,13 +106,12 @@ private IEnumerable<FlammeRougeRacingCategory> ParseCategories(StringValues cate
84106
}
85107
}
86108

87-
private async Task<List<ZwiftRaceResultEntryResponse>> GetRacesResults(
88-
List<long> riders,
89-
string[] urls,
109+
private async Task<Dictionary<ZwiftEventEventSubgroupKey, List<ZwiftRaceResultEntryResponse>>> GetRacesResults(
110+
HashSet<long> riders,
111+
HashSet<string> urls,
90112
CancellationToken cancellationToken)
91113
{
92-
var ridersSet = riders.ToHashSet();
93-
List<ZwiftRaceResultEntryResponse> acc = [];
114+
Dictionary<ZwiftEventEventSubgroupKey, List<ZwiftRaceResultEntryResponse>> result = [];
94115

95116
foreach (var url in urls)
96117
{
@@ -108,22 +129,41 @@ private async Task<List<ZwiftRaceResultEntryResponse>> GetRacesResults(
108129
// early exit if no results
109130
if (subgroupResults.Entries.Count == 0)
110131
{
111-
return acc;
132+
return result;
112133
}
113134

114135
var resultsPortion = subgroupResults.Entries
115-
.Where(x => ridersSet.Contains(x.ProfileId))
136+
.Where(x => riders.Contains(x.ProfileId))
116137
.ToList();
117138

118-
acc.AddRange(resultsPortion);
139+
if (resultsPortion.Count == 0)
140+
{
141+
continue;
142+
}
143+
144+
var key = new ZwiftEventEventSubgroupKey
145+
{
146+
Event = @event,
147+
Subgroup = zwiftEventSubgroup,
148+
SubgroupResults = subgroupResults
149+
};
150+
151+
result.Add(key, resultsPortion);
119152
}
120153
}
121154

122-
return acc;
155+
return result;
156+
}
157+
158+
private record ZwiftEventEventSubgroupKey
159+
{
160+
public required ZwiftEventResponse Event { get; init; }
161+
public required ZwiftEventSubgroupResponse Subgroup { get; init; }
162+
public required ZwiftRaceResultResponse SubgroupResults { get; init; }
123163
}
124164
}
125165

126166
public sealed record ZwiftFRRTourStageResultsRequest
127167
{
128-
public required string[] Urls { get; init; }
168+
public required HashSet<string> Urls { get; init; }
129169
}

src/FitSyncHub.Zwift/HttpClients/Models/Responses/Events/ZwiftEventResponse.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace FitSyncHub.Zwift.HttpClients.Models.Responses.Events;
1+
using System.Text.Json.Serialization;
2+
3+
namespace FitSyncHub.Zwift.HttpClients.Models.Responses.Events;
24

35
public sealed record ZwiftEventResponse
46
{
@@ -12,6 +14,10 @@ public sealed record ZwiftEventResponse
1214
public required double? DistanceInMeters { get; init; }
1315
public required ZwiftEventSubgroupResponse[] EventSubgroups { get; init; }
1416
public required ZwiftEvenSeriesResponse? EventSeries { get; init; }
17+
public required string[] RulesSet { get; init; }
18+
19+
[JsonIgnore]
20+
public bool IsITT => RulesSet.Contains("NO_DRAFTING");
1521
}
1622

1723
public sealed record ZwiftEventSubgroupResponse

0 commit comments

Comments
 (0)