/// <summary>
        /// Parses a detailed summary entry into a scoreboard details object.
        /// </summary>
        /// <param name="dataEntries">The data.</param>
        protected virtual void ParseDetailedSummaryEntry(ScoreboardDetails details, string[] dataEntries)
        {
            var summary = new ScoreboardSummaryEntry();

            details.Summary = summary;
            // ID, Division (labeled location, their bug), Location (labeled division, their bug), tier, scored image count, play time, score time, warnings, current score
            summary.TeamId = TeamId.Parse(dataEntries[0]);
            // [not in data, matched from categoryProvider] all service category
            summary.Category = _categoryProvider?.GetCategory(summary.TeamId);
            // tier and division
            if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[2], out Division division))
            {
                summary.Division = division;
            }
            summary.Location = dataEntries[1];
            if (Enum.TryParse <Tier>(dataEntries[3], true, out Tier tier))
            {
                summary.Tier = tier;
            }
            // number of images
            summary.ImageCount = int.Parse(dataEntries[4].Trim());
            // times
            summary.PlayTime  = Utilities.ParseHourMinuteSecondTimespan(dataEntries[5]);
            details.ScoreTime = Utilities.ParseHourMinuteSecondTimespan(dataEntries[6]);
            // warnings and total score
            string warnStr = dataEntries[7];

            summary.Warnings  |= warnStr.Contains("T") ? ScoreWarnings.TimeOver : 0;
            summary.Warnings  |= warnStr.Contains("M") ? ScoreWarnings.MultiImage : 0;
            summary.TotalScore = double.Parse(dataEntries.Last().Trim());
        }
示例#2
0
        private bool CategoryEquals(ScoreboardSummaryEntry team, string category)
        {
            if (team.Category == null)
            {
                return(category == null);
            }

            string teamCategory = team.Category.Trim().ToLowerInvariant();

            category = category.Trim().ToLowerInvariant();
            if (teamCategory.Equals(category))
            {
                return(true);
            }
            if (teamCategory.StartsWith(category))
            {
                // starts to get really soft from this comparison onwards
                return(true);
            }
            if (teamCategory.StartsWithWhereElement(c => c != ' ', category))
            {
                return(true);
            }
            // last check /shrug
            return(teamCategory.Replace(" ", "").Contains(category.Replace(" ", "")));
        }
示例#3
0
        protected string AbbreviateDivision(ScoreboardSummaryEntry team)
        {
            if (!team.Category.HasValue || team.Division != Division.AllService)
            {
                return(team.Division.ToConciseString());
            }

            return("AS:" + CyberPatriot.Models.Serialization.ServiceCategoryExtensions.Abbreviate(team.Category.Value));
        }
        public string CreatePeerLeaderboardEmbed(TeamId teamId, CompleteScoreboardSummary scoreboard, IList <ScoreboardSummaryEntry> peerTeams, TimeZoneInfo timeZone = null, int topTeams = 3, int nearbyTeams = 5)
        {
            // var peerTeams = CompetitionLogic.GetPeerTeams(ScoreRetriever.Round, scoreboard, teamDetails.Summary);
            var stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("**CyberPatriot Scoreboard**");
            DateTimeOffset timestamp = timeZone == null ? scoreboard.SnapshotTimestamp : TimeZoneInfo.ConvertTime(scoreboard.SnapshotTimestamp, timeZone);

            stringBuilder.AppendFormat("*Competing against: {0} | As of: ", teamId);
            stringBuilder.AppendFormat("{0:g}", timestamp);
            stringBuilder.Append(' ').Append(timeZone == null ? "UTC" : TimeZoneNames.TZNames.GetAbbreviationsForTimeZone(timeZone.Id, "en-US").Generic).AppendLine("*");
            stringBuilder.AppendLine("```bash");
            // zero-based rank of the given team
            int pos = peerTeams.IndexOfWhere(team => team.TeamId == teamId);

            if (pos < nearbyTeams + topTeams + 1)
            {
                peerTeams.Take(nearbyTeams + pos + 1)
                .Select((team, i) => stringBuilder.AppendFormat("{8}{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier, team.TeamId == teamId ? ">" : "#").AppendLine())
                .Consume();
            }
            else
            {
                peerTeams.Take(topTeams)
                .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine())
                .Consume();
                stringBuilder.AppendLine("...");
                peerTeams.Skip(pos - nearbyTeams)
                .Take(nearbyTeams)
                .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + pos - nearbyTeams + 1, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine())
                .Consume();
                ScoreboardSummaryEntry thisTeamDetails = peerTeams.Single(t => t.TeamId == teamId);
                stringBuilder.AppendFormat(">{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", pos + 1, thisTeamDetails.TeamId, thisTeamDetails.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(thisTeamDetails.TotalScore), thisTeamDetails.Advancement.HasValue ? thisTeamDetails.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", thisTeamDetails.PlayTime), thisTeamDetails.Warnings.ToConciseString(), thisTeamDetails.Division.ToConciseString(), thisTeamDetails.Tier).AppendLine();
                // since pos and i are both zero-based, i + pos + 2 returns correct team rank for teams after given team
                peerTeams.Skip(pos + 1)
                .Take(nearbyTeams)
                .Select((team, i) => stringBuilder.AppendFormat("#{0,-5}{1,-7}{2,4}{6,6}{7,10}{3,16}{4,7}{5,4}", i + pos + 2, team.TeamId, team.Location, ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore), team.Advancement.HasValue ? team.Advancement.Value.ToConciseString() : string.Format("{0:hh\\:mm}", team.PlayTime), team.Warnings.ToConciseString(), team.Division.ToConciseString(), team.Tier).AppendLine())
                .Consume();
            }

            stringBuilder.AppendLine("```");
            if (scoreboard.OriginUri != null)
            {
                stringBuilder.AppendLine(scoreboard.OriginUri.ToString());
            }
            return(stringBuilder.ToString());
        }
        public override ScoreboardFilterInfo GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamDetails)
        {
            if (teamDetails.Division == Division.MiddleSchool)
            {
                // middle school doesn't have tiers or categories
                return(new ScoreboardFilterInfo(Division.MiddleSchool, null));
            }

            // open/service

            if ((teamDetails.Division == Division.Open && round > CompetitionRound.Round2) || (teamDetails.Division == Division.AllService && round == CompetitionRound.Round3))
            {
                // In open past R2, tier matters, but that's it
                // In all service R3, category doesn't* matter, just tier
                // See issue #14
                return(new ScoreboardFilterInfo(teamDetails.Division, teamDetails.Tier));
            }

            // open/service, service: category matters; open: no tiers
            if (teamDetails.Division == Division.Open)
            {
                // unknown round - if our candidate team has a tier, filter by tier, otherwise return the whole division
                if (round == 0 && teamDetails.Tier != null)
                {
                    return(new ScoreboardFilterInfo(teamDetails.Division, teamDetails.Tier));
                }

                // either R1 or R2
                // safe to return the whole division as a peer list
                return(new ScoreboardFilterInfo(Division.Open, null));
            }

            // all-service round where category matters ("R0" we default to factoring in category)

            Tier?tierFilter = null;

            // filter by tier, where available
            if (round > CompetitionRound.Round2)
            {
                tierFilter = teamDetails.Tier;
            }

            // there might be some A.S. teams whose categories we don't know
            // they get treated as not-my-problem, that is, not part of my category
            // unknown category -> null -> no filtering on that, a good enough fallback
            return(new ScoreboardFilterInfo(Division.AllService, tierFilter, teamDetails.Category, null));
        }
示例#6
0
        private string GetImageLeaderboardEntry(ScoreboardSummaryEntry team, ScoreboardImageDetails image, int friendlyIndex, bool useAbbreviatedDivision = false, string prefix = "#")
        {
            string divisionFormatString = useAbbreviatedDivision ? "{0,6}" : "  {0,-10}";
            string vulnPenString        = new string(' ', 10);

            if (ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.VulnerabilityDisplay, image.VulnerabilitiesFound, image.VulnerabilitiesRemaining))
            {
                vulnPenString = $"{image.VulnerabilitiesFound,5}v {image.Penalties,2}p";
            }

            return($"{prefix}{friendlyIndex,-5}{team.TeamId,-7}{team.Location,4}" +
                   string.Format(divisionFormatString, AbbreviateDivision(team)) + $"{team.Tier,10}" +
                   $"{ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(image.Score),13}" +
                   vulnPenString +
                   $"{(ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.TimeDisplay, image.PlayTime) ? image.PlayTime.ToHoursMinutesSecondsString() : ""),7}" +
                   $"{image.Warnings.ToConciseString(),4}");
        }
        protected virtual ScoreboardSummaryEntry ParseSummaryEntry(string[] dataEntries)
        {
            ScoreboardSummaryEntry summary = new ScoreboardSummaryEntry();

            summary.TeamId   = TeamId.Parse(dataEntries[0]);
            summary.Category = _categoryProvider?.GetCategory(summary.TeamId);
            summary.Location = dataEntries[1];
            if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[2], out Division division))
            {
                summary.Division = division;
            }
            if (Enum.TryParse <Tier>(dataEntries[3]?.Trim(), true, out Tier tier))
            {
                summary.Tier = tier;
            }
            summary.ImageCount = int.Parse(dataEntries[4]);
            summary.PlayTime   = Utilities.ParseHourMinuteTimespan(dataEntries[5]);
            summary.TotalScore = int.Parse(dataEntries[7]);
            summary.Warnings  |= dataEntries[6].Contains("T") ? ScoreWarnings.TimeOver : 0;
            summary.Warnings  |= dataEntries[6].Contains("M") ? ScoreWarnings.MultiImage : 0;
            return(summary);
        }
        protected override void ParseDetailedSummaryEntry(ScoreboardDetails details, string[] dataEntries)
        {
            var summary = new ScoreboardSummaryEntry();

            details.Summary = summary;
            // ID, Division (labeled location, their bug), Location (labeled division, their bug), tier, scored img, play time, score time, current score, warn
            summary.TeamId   = TeamId.Parse(dataEntries[0]);
            summary.Category = _categoryProvider?.GetCategory(summary.TeamId);
            if (Utilities.TryParseEnumSpaceless <Division>(dataEntries[1], out Division division))
            {
                summary.Division = division;
            }
            summary.Location = dataEntries[2];
            if (Enum.TryParse <Tier>(dataEntries[3], true, out Tier tier))
            {
                summary.Tier = tier;
            }
            summary.ImageCount = int.Parse(dataEntries[4].Trim());
            summary.PlayTime   = Utilities.ParseHourMinuteTimespan(dataEntries[5]);
            string scoreTimeText = dataEntries[6];
            // to deal with legacy scoreboards
            int scoreTimeIndOffset = 0;

            if (scoreTimeText.Contains(":"))
            {
                details.ScoreTime = Utilities.ParseHourMinuteTimespan(dataEntries[6]);
            }
            else
            {
                details.ScoreTime  = summary.PlayTime;
                scoreTimeIndOffset = -1;
            }
            summary.TotalScore = int.Parse(dataEntries[7 + scoreTimeIndOffset].Trim());
            string warnStr = dataEntries[8 + scoreTimeIndOffset];

            summary.Warnings |= warnStr.Contains("T") ? ScoreWarnings.TimeOver : 0;
            summary.Warnings |= warnStr.Contains("M") ? ScoreWarnings.MultiImage : 0;
        }
示例#9
0
 public abstract IList <ScoreboardSummaryEntry> GetPeerTeams(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamInfo);
示例#10
0
 public virtual string GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => team.Category ?? team.Division.ToStringCamelCaseToSpace();
示例#11
0
 ScoreboardFilterInfo ICompetitionRoundLogicService.GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamInfo) => underlyingService.GetPeerFilter(round, teamInfo);
示例#12
0
 string ICompetitionRoundLogicService.GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => underlyingService.GetEffectiveDivisionDescriptor(team);
示例#13
0
        async Task TeamPlacementChangeNotificationTimer(TimerStateWrapper state)
        {
            try
            {
                using (var databaseContext = _database.OpenContext <Models.Guild>(false))
                    using (var guildSettingEnumerator = databaseContext.FindAllAsync().GetEnumerator())
                    {
                        CompleteScoreboardSummary masterScoreboard     = null;
                        Dictionary <TeamId, int>  teamIdsToPeerIndexes = new Dictionary <TeamId, int>();
                        while (await guildSettingEnumerator.MoveNext().ConfigureAwait(false))
                        {
                            Models.Guild guildSettings = guildSettingEnumerator.Current;

                            if (guildSettings?.ChannelSettings == null || guildSettings.ChannelSettings.Count == 0)
                            {
                                return;
                            }

                            IGuild guild = _discord.GetGuild(guildSettings.Id);
                            foreach (var chanSettings in guildSettings.ChannelSettings.Values)
                            {
                                if (chanSettings?.MonitoredTeams == null || chanSettings.MonitoredTeams.Count == 0)
                                {
                                    continue;
                                }

                                IGuildChannel rawChan = await guild.GetChannelAsync(chanSettings.Id).ConfigureAwait(false);

                                if (!(rawChan is ITextChannel chan))
                                {
                                    continue;
                                }

                                masterScoreboard = await _scoreRetriever.GetScoreboardAsync(ScoreboardFilterInfo.NoFilter).ConfigureAwait(false);

                                foreach (TeamId monitored in chanSettings.MonitoredTeams)
                                {
                                    int masterScoreboardIndex =
                                        masterScoreboard.TeamList.IndexOfWhere(scoreEntry => scoreEntry.TeamId == monitored);
                                    if (masterScoreboardIndex == -1)
                                    {
                                        continue;
                                    }

                                    // TODO efficiency: we're refiltering every loop iteration
                                    ScoreboardSummaryEntry monitoredEntry = masterScoreboard.TeamList[masterScoreboardIndex];
                                    int peerIndex = masterScoreboard.Clone().WithFilter(_competitionLogic.GetPeerFilter(_scoreRetriever.Round, monitoredEntry)).TeamList.IndexOf(monitoredEntry);
                                    teamIdsToPeerIndexes[monitored] = peerIndex;

                                    // we've obtained all information, now compare to past data
                                    if (state.PreviousTeamListIndexes != null &&
                                        state.PreviousTeamListIndexes.TryGetValue(monitored, out int prevPeerIndex))
                                    {
                                        int indexDifference = peerIndex - prevPeerIndex;
                                        if (indexDifference != 0)
                                        {
                                            StringBuilder announceMessage = new StringBuilder();
                                            announceMessage.Append("**");
                                            announceMessage.Append(monitored);
                                            announceMessage.Append("**");
                                            if (indexDifference > 0)
                                            {
                                                announceMessage.Append(" rose ");
                                            }
                                            else
                                            {
                                                announceMessage.Append(" fell ");
                                                indexDifference *= -1;
                                            }

                                            var teamDetails = await _scoreRetriever.GetDetailsAsync(monitored).ConfigureAwait(false);

                                            announceMessage.Append(Utilities.Pluralize("place", indexDifference));
                                            announceMessage.Append(" to **");
                                            announceMessage.Append(Utilities.AppendOrdinalSuffix(peerIndex + 1));
                                            announceMessage.Append(" place**.");
                                            await chan.SendMessageAsync(
                                                announceMessage.ToString(),
                                                embed : _messageBuilder
                                                .CreateTeamDetailsEmbed(
                                                    teamDetails,
                                                    masterScoreboard,
                                                    _competitionLogic.GetPeerFilter(_scoreRetriever.Round, teamDetails.Summary))
                                                .Build()).ConfigureAwait(false);
                                        }
                                    }
                                }
                            }
                        }

                        state.PreviousTeamListIndexes = teamIdsToPeerIndexes;
                    }
            }
            catch (Exception ex)
            {
                await _logService.LogApplicationMessageAsync(LogSeverity.Error, "Error in team monitor timer task", ex).ConfigureAwait(false);
            }
        }
 public abstract ScoreboardFilterInfo GetPeerFilter(CompetitionRound round, ScoreboardSummaryEntry teamInfo);
示例#15
0
        public string CreatePeerLeaderboardEmbed(TeamId teamId, CompleteScoreboardSummary peerScoreboard, TimeZoneInfo timeZone = null, int topTeams = 3, int nearbyTeams = 5)
        {
            // var peerTeams = CompetitionLogic.GetPeerTeams(ScoreRetriever.Round, scoreboard, teamDetails.Summary);
            var stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("**CyberPatriot Scoreboard**");
            DateTimeOffset timestamp = timeZone == null ? peerScoreboard.SnapshotTimestamp : TimeZoneInfo.ConvertTime(peerScoreboard.SnapshotTimestamp, timeZone);

            stringBuilder.AppendFormat("*Competing against: {0} | As of: ", teamId);
            stringBuilder.AppendFormat("{0:g}", timestamp);
            stringBuilder.Append(' ').Append(timeZone == null ? "UTC" : TimeZoneNames.TZNames.GetAbbreviationsForTimeZone(timeZone.Id, "en-US").Generic).AppendLine("*");

            var peerTeams = peerScoreboard.TeamList;

            stringBuilder.AppendFormat("*{0} competes in: {1} Division", teamId, peerScoreboard.Filter.Division.ToStringCamelCaseToSpace());
            if (peerScoreboard.Filter.Category.HasValue)
            {
                stringBuilder.AppendFormat(", {0}", peerScoreboard.Filter.Category.Value.ToCanonicalName());
            }
            if (peerScoreboard.Filter.Tier.HasValue)
            {
                stringBuilder.AppendFormat(", {0} Tier", peerScoreboard.Filter.Tier.Value);
            }
            if (peerScoreboard.Filter.Location != null)
            {
                stringBuilder.AppendFormat(", {0}", peerScoreboard.Filter.Location);
            }
            stringBuilder.AppendLine("*");

            bool conciseDivision = !peerTeams.Any(x => x.Category != null);

            stringBuilder.AppendLine("```bash");
            // zero-based rank of the given team
            int pos = peerTeams.IndexOfWhere(team => team.TeamId == teamId);

            if (pos < nearbyTeams + topTeams + 1)
            {
                peerTeams.Take(nearbyTeams + pos + 1)
                .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + 1, useAbbreviatedDivision: conciseDivision, prefix: team.TeamId == teamId ? ">" : "#")))
                .Consume();
            }
            else
            {
                peerTeams.Take(topTeams)
                .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + 1, useAbbreviatedDivision: conciseDivision)))
                .Consume();
                stringBuilder.AppendLine("...");
                peerTeams.Skip(pos - nearbyTeams)
                .Take(nearbyTeams)
                .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + pos - nearbyTeams + 1, useAbbreviatedDivision: conciseDivision)))
                .Consume();
                ScoreboardSummaryEntry thisTeamDetails = peerTeams.Single(t => t.TeamId == teamId);
                stringBuilder.AppendLine(GetTeamLeaderboardEntry(thisTeamDetails, pos + 1, useAbbreviatedDivision: conciseDivision, prefix: ">"));
                // since pos and i are both zero-based, i + pos + 2 returns correct team rank for teams after given team
                peerTeams.Skip(pos + 1)
                .Take(nearbyTeams)
                .Select((team, i) => stringBuilder.AppendLine(GetTeamLeaderboardEntry(team, i + pos + 2, useAbbreviatedDivision: conciseDivision)))
                .Consume();
            }

            stringBuilder.AppendLine("```");
            if (peerScoreboard.OriginUri != null)
            {
                stringBuilder.AppendLine(peerScoreboard.OriginUri.ToString());
            }
            return(stringBuilder.ToString());
        }
示例#16
0
        public virtual TeamDetailRankingInformation GetRankingInformation(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamInfo)
        {
            divisionScoreboard = divisionScoreboard.Clone().WithFilter(teamInfo.Division, null);

            // may be equal to division scoreboard, that's fine
            var tierScoreboard = divisionScoreboard.Clone().WithFilter(teamInfo.Division, teamInfo.Tier);
            var peers          = GetPeerTeams(round, divisionScoreboard, teamInfo);

            var summaryComparer = BuildSummaryComparer(teamInfo.TeamId);

            return(new TeamDetailRankingInformation()
            {
                TeamId = teamInfo.TeamId,
                Peers = peers,
                PeerIndex = peers.IndexOfWhere(summaryComparer),
                PeerCount = peers.Count,
                DivisionIndex = divisionScoreboard.TeamList.IndexOfWhere(summaryComparer),
                DivisionCount = divisionScoreboard.TeamList.Count,
                TierIndex = tierScoreboard.TeamList.IndexOfWhere(summaryComparer),
                TierCount = tierScoreboard.TeamList.Count
            });
        }
示例#17
0
        private string GetTeamLeaderboardEntry(ScoreboardSummaryEntry team, int friendlyIndex, bool useAbbreviatedDivision = false, string prefix = "#", bool showAdvancement = true)
        {
            string divisionFormatString = useAbbreviatedDivision ? "{0,6}" : "  {0,-10}";

            return($"{prefix}{friendlyIndex,-5}{team.TeamId,-7}{team.Location,4}" + string.Format(divisionFormatString, AbbreviateDivision(team)) + $"{team.Tier,10}{ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(team.TotalScore),16}{((showAdvancement && team.Advancement.HasValue) ? team.Advancement.Value.ToConciseString() : (ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.TimeDisplay, team.PlayTime) ? team.PlayTime.ToHoursMinutesSecondsString() : "")),10}{team.Warnings.ToConciseString(),4}");
        }
示例#18
0
        public override IList <ScoreboardSummaryEntry> GetPeerTeams(CompetitionRound round, CompleteScoreboardSummary divisionScoreboard, ScoreboardSummaryEntry teamDetails)
        {
            // make a clone because we'll mutate this later
            divisionScoreboard = divisionScoreboard.Clone().WithFilter(teamDetails.Division, null);
            if (teamDetails.Division == Division.MiddleSchool)
            {
                // middle school doesn't have tiers or categories
                return(divisionScoreboard.TeamList);
            }

            // open/service

            if ((teamDetails.Division == Division.Open && round > CompetitionRound.Round2) || (teamDetails.Division == Division.AllService && round == CompetitionRound.Round3))
            {
                // In open past R2, tier matters, but that's it
                // In all service R3, category doesn't* matter, just tier
                // See issue #14
                return(divisionScoreboard.WithFilter(teamDetails.Division, teamDetails.Tier).TeamList);
            }

            // open/service, service: category matters; open: no tiers
            if (teamDetails.Division == Division.Open)
            {
                // unknown round - if our candidate team has a tier, filter by tier, otherwise return the whole division
                if (round == 0 && teamDetails.Tier != null)
                {
                    return(divisionScoreboard.WithFilter(teamDetails.Division, teamDetails.Tier).TeamList);
                }

                // either R1 or R2
                // safe to return the whole division as a peer list
                return(divisionScoreboard.TeamList);
            }

            // all-service round where category matters ("R0" we default to factoring in category)

            // filter by tier, where available
            if (round > CompetitionRound.Round2)
            {
                divisionScoreboard.WithFilter(Division.AllService, teamDetails.Tier);
            }

            // just need to filter the list by category
            if (teamDetails.Category == null)
            {
                // silent fail
                return(divisionScoreboard.TeamList);
            }

            // there might be some A.S. teams whose categories we don't know
            // they get treated as not-my-problem, that is, not part of my category
            return(divisionScoreboard.TeamList.Where(t => t.Category == teamDetails.Category).ToIList());
        }
 public virtual string GetEffectiveDivisionDescriptor(ScoreboardSummaryEntry team) => team.Category.HasValue ? CyberPatriot.Models.Serialization.ServiceCategoryExtensions.ToCanonicalName(team.Category.Value) : team.Division.ToStringCamelCaseToSpace();