Update release notes and deploy workspace
This commit is contained in:
@@ -208,6 +208,23 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
item.Candidate.Platform))
|
||||
.ToArrayAsync();
|
||||
|
||||
var workflowRules = WorkflowRuleSettings.Read(season, settings);
|
||||
var votingEntryRows = await db.VoteEntries
|
||||
.AsNoTracking()
|
||||
.Where(item => item.Category.SeasonId == seasonId)
|
||||
.Select(item => new AdminVotingEntryRow(
|
||||
item.BallotId,
|
||||
item.CategoryId,
|
||||
item.CandidateId))
|
||||
.ToArrayAsync();
|
||||
var votingWorkspace = BuildVotingWorkspace(
|
||||
categories,
|
||||
candidates,
|
||||
pendingNominations,
|
||||
resultItems,
|
||||
votingEntryRows,
|
||||
workflowRules);
|
||||
|
||||
var clipSubmissions = await db.ClipSubmissions
|
||||
.AsNoTracking()
|
||||
.Where(item => item.SeasonId == seasonId)
|
||||
@@ -232,7 +249,7 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
season.Id,
|
||||
season.Year,
|
||||
season.Name,
|
||||
NormalizeSeasonStreamUrl(season.ShowStreamUrl),
|
||||
season.IsDemo,
|
||||
season.CurrentPhase,
|
||||
season.IsCurrent,
|
||||
season.IsCommunityOnly,
|
||||
@@ -244,6 +261,8 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
season.ReviewEndsAt,
|
||||
season.ShowDate,
|
||||
season.ShowStartsAt,
|
||||
season.WinnersPublishedAt,
|
||||
season.WinnersPublishedByTwitchId,
|
||||
subcategoryTemplates,
|
||||
categories,
|
||||
candidates,
|
||||
@@ -253,9 +272,15 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
settings?.TrackingReviewNotes ?? string.Empty,
|
||||
trackingRules.Source.ShowManualReviewNotesInReview,
|
||||
resultItems,
|
||||
votingWorkspace,
|
||||
clipSubmissions));
|
||||
}
|
||||
|
||||
private sealed record AdminVotingEntryRow(
|
||||
int BallotId,
|
||||
int CategoryId,
|
||||
int CandidateId);
|
||||
|
||||
private sealed record AdminNominationRow(
|
||||
int Id,
|
||||
int? CategoryId,
|
||||
@@ -330,6 +355,165 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
item.ReviewedAt);
|
||||
}
|
||||
|
||||
private static AdminVotingWorkspaceDto BuildVotingWorkspace(
|
||||
AdminCategoryItemDto[] categories,
|
||||
AdminCandidateItemDto[] candidates,
|
||||
AdminNominationReviewItemDto[] pendingNominations,
|
||||
AdminAwardResultItemDto[] results,
|
||||
AdminVotingEntryRow[] voteEntries,
|
||||
WorkflowRuleSetting[] workflowRules)
|
||||
{
|
||||
var resultMap = results.ToDictionary(item => item.CategoryId);
|
||||
var totalBallots = voteEntries.Select(item => item.BallotId).Distinct().Count();
|
||||
var voteCountByCategory = voteEntries
|
||||
.GroupBy(item => item.CategoryId)
|
||||
.ToDictionary(group => group.Key, group => group.Count());
|
||||
var ballotCountByCategory = voteEntries
|
||||
.GroupBy(item => item.CategoryId)
|
||||
.ToDictionary(group => group.Key, group => group.Select(item => item.BallotId).Distinct().Count());
|
||||
var voteCountByCandidate = voteEntries
|
||||
.GroupBy(item => (item.CategoryId, item.CandidateId))
|
||||
.ToDictionary(group => group.Key, group => group.Count());
|
||||
|
||||
var winnerPlacementsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.MaxWinnerPlacements);
|
||||
var recommendedNominatorsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.RecommendedNominatorsPerSubcategory);
|
||||
var winnerRequiresClipRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.WinnerRequiresClip);
|
||||
|
||||
var workspaceItems = categories
|
||||
.Select(category =>
|
||||
{
|
||||
var categoryCandidates = candidates
|
||||
.Where(candidate => candidate.CategoryId == category.Id)
|
||||
.ToArray();
|
||||
var categoryVoteCount = voteCountByCategory.TryGetValue(category.Id, out var voteCount) ? voteCount : 0;
|
||||
var categoryBallotCount = ballotCountByCategory.TryGetValue(category.Id, out var ballotCount) ? ballotCount : 0;
|
||||
var nominationCount = categoryCandidates.Sum(candidate => Math.Max(candidate.NominationTally, 0));
|
||||
var openReviewCount = pendingNominations.Count(item =>
|
||||
item.CategoryId == category.Id
|
||||
|| item.SuggestedCategoryId == category.Id);
|
||||
var existingResult = resultMap.GetValueOrDefault(category.Id);
|
||||
var maxVotes = categoryCandidates.Length == 0
|
||||
? 0
|
||||
: categoryCandidates.Max(candidate => voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0));
|
||||
var topVoteTieCount = maxVotes <= 0
|
||||
? 0
|
||||
: categoryCandidates.Count(candidate => voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0) == maxVotes);
|
||||
|
||||
var leaderboard = categoryCandidates
|
||||
.Select(candidate =>
|
||||
{
|
||||
var candidateVotes = voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0);
|
||||
var hasWinnerConflict = CandidateHasWinnerConflict(candidate, category.Id, results, winnerPlacementsRule);
|
||||
return new AdminVotingCandidateRankDto(
|
||||
candidate.Id,
|
||||
candidate.DisplayName,
|
||||
candidate.ChannelSlug,
|
||||
candidate.Platform,
|
||||
candidateVotes,
|
||||
categoryVoteCount > 0 ? (int)Math.Round(candidateVotes * 100d / categoryVoteCount) : 0,
|
||||
Math.Max(candidate.NominationTally, 0),
|
||||
!string.IsNullOrWhiteSpace(candidate.ClipCompilationUrl),
|
||||
string.IsNullOrWhiteSpace(candidate.ClipEmbedStatus) ? "unchecked" : candidate.ClipEmbedStatus,
|
||||
hasWinnerConflict,
|
||||
existingResult?.CandidateId == candidate.Id,
|
||||
!string.Equals(candidate.AcceptanceStatus, "declined", StringComparison.OrdinalIgnoreCase),
|
||||
maxVotes > 0 && candidateVotes == maxVotes && topVoteTieCount > 1);
|
||||
})
|
||||
.OrderByDescending(item => item.Votes)
|
||||
.ThenByDescending(item => item.NominationTally)
|
||||
.ThenBy(item => item.DisplayName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var readyCandidates = leaderboard.Count(item => item.IsAccepted);
|
||||
var leadingCandidate = leaderboard.FirstOrDefault();
|
||||
var hasMissingClip = winnerRequiresClipRule.Enabled
|
||||
&& leadingCandidate is not null
|
||||
&& !leadingCandidate.HasClip;
|
||||
var hasRuleConflict = leadingCandidate?.HasWinnerConflict ?? false;
|
||||
var hasOpenReviews = openReviewCount > 0;
|
||||
var hasSoftNominatorWarning = recommendedNominatorsRule.Enabled
|
||||
&& nominationCount < Math.Max(1, recommendedNominatorsRule.Limit);
|
||||
var winnerReady = existingResult is not null
|
||||
|| leadingCandidate is not null
|
||||
&& leadingCandidate.IsAccepted
|
||||
&& !hasMissingClip
|
||||
&& !hasRuleConflict
|
||||
&& !hasOpenReviews;
|
||||
|
||||
return new AdminVotingCategoryWorkspaceItemDto(
|
||||
category.Id,
|
||||
category.GroupName,
|
||||
category.Name,
|
||||
category.SortOrder,
|
||||
category.ViewerRangeMin,
|
||||
category.ViewerRangeMax,
|
||||
categoryVoteCount,
|
||||
categoryBallotCount,
|
||||
categoryCandidates.Length,
|
||||
readyCandidates,
|
||||
nominationCount,
|
||||
openReviewCount,
|
||||
existingResult is not null,
|
||||
winnerReady,
|
||||
topVoteTieCount > 1,
|
||||
hasMissingClip,
|
||||
hasRuleConflict,
|
||||
hasOpenReviews,
|
||||
hasSoftNominatorWarning,
|
||||
leaderboard);
|
||||
})
|
||||
.OrderBy(item => item.SortOrder)
|
||||
.ThenBy(item => item.GroupName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(item => item.CategoryName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var summary = new AdminVotingWorkspaceSummaryDto(
|
||||
voteEntries.Length,
|
||||
totalBallots,
|
||||
workspaceItems.Length,
|
||||
workspaceItems.Count(item => item.VoteCount > 0),
|
||||
workspaceItems.Count(item => item.WinnerReady),
|
||||
workspaceItems.Count(item =>
|
||||
item.HasMissingClip
|
||||
|| item.HasRuleConflict
|
||||
|| item.HasOpenReviews
|
||||
|| item.HasTopVoteTie),
|
||||
workspaceItems.Count(item => item.HasWinner));
|
||||
|
||||
return new AdminVotingWorkspaceDto(summary, workspaceItems);
|
||||
}
|
||||
|
||||
private static bool CandidateHasWinnerConflict(
|
||||
AdminCandidateItemDto candidate,
|
||||
int categoryId,
|
||||
AdminAwardResultItemDto[] results,
|
||||
WorkflowRuleSetting winnerPlacementsRule)
|
||||
{
|
||||
if (!winnerPlacementsRule.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var identityKey = candidate.StreamerIdentityId.HasValue
|
||||
? $"identity:{candidate.StreamerIdentityId.Value}"
|
||||
: WorkflowRuleSettings.CandidateIdentityKey(candidate.DisplayName, candidate.ChannelSlug);
|
||||
|
||||
var existingWinnerCount = results.Count(result =>
|
||||
{
|
||||
if (result.CategoryId == categoryId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var resultIdentityKey = result.StreamerIdentityId.HasValue
|
||||
? $"identity:{result.StreamerIdentityId.Value}"
|
||||
: WorkflowRuleSettings.CandidateIdentityKey(result.CandidateDisplayName, result.CandidateChannelSlug);
|
||||
return string.Equals(resultIdentityKey, identityKey, StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
return existingWinnerCount >= winnerPlacementsRule.Limit;
|
||||
}
|
||||
|
||||
private static AdminNominationReviewGroupDto[] BuildNominationReviewGroups(
|
||||
IEnumerable<AdminNominationRow> rows,
|
||||
IEnumerable<dynamic> categoryRows,
|
||||
|
||||
Reference in New Issue
Block a user