< Summary

Information
Class: EHonda.KicktippAi.Core.MatchContextDocumentCatalog.MatchContextDocumentPolicy
Assembly: EHonda.KicktippAi.Core
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Core/MatchContextDocumentCatalog.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 226
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Core/MatchContextDocumentCatalog.cs

#LineLine coverage
 1using System.Text;
 2using System.Text.RegularExpressions;
 3
 4namespace EHonda.KicktippAi.Core;
 5
 6public sealed record MatchContextDocumentSelection(
 7    IReadOnlyList<string> RequiredDocumentNames,
 8    IReadOnlyList<string> OptionalDocumentNames);
 9
 10public static class MatchContextDocumentCatalog
 11{
 112    private sealed record MatchContextDocumentPolicy(
 113        bool IncludeCommunityRules,
 114        bool IncludeRecentHistory,
 115        bool IncludeFifaRankings,
 116        bool IncludeLineups,
 117        bool IncludeHomeAwayHistory,
 118        bool IncludeHeadToHead,
 119        bool IncludeTransfers);
 20
 21    private static readonly MatchContextDocumentPolicy BundesligaPolicy = new(
 22        IncludeCommunityRules: true,
 23        IncludeRecentHistory: true,
 24        IncludeFifaRankings: false,
 25        IncludeLineups: false,
 26        IncludeHomeAwayHistory: true,
 27        IncludeHeadToHead: true,
 28        IncludeTransfers: true);
 29
 30    private static readonly MatchContextDocumentPolicy WorldCup2026Policy = new(
 31        IncludeCommunityRules: true,
 32        IncludeRecentHistory: true,
 33        IncludeFifaRankings: true,
 34        IncludeLineups: true,
 35        IncludeHomeAwayHistory: false,
 36        IncludeHeadToHead: false,
 37        IncludeTransfers: false);
 38
 39    private static readonly IReadOnlyDictionary<string, MatchContextDocumentPolicy> CommunityPolicies =
 40        new Dictionary<string, MatchContextDocumentPolicy>(StringComparer.OrdinalIgnoreCase)
 41        {
 42            ["ehonda-dev-wm26"] = WorldCup2026Policy
 43        };
 44
 45    private static readonly IReadOnlyDictionary<string, string> TeamAbbreviations =
 46        new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
 47        {
 48            { "1. FC Heidenheim 1846", "fch" },
 49            { "1. FC Köln", "fck" },
 50            { "1. FC Union Berlin", "fcu" },
 51            { "1899 Hoffenheim", "tsg" },
 52            { "Bayer 04 Leverkusen", "b04" },
 53            { "Bor. Mönchengladbach", "bmg" },
 54            { "Borussia Dortmund", "bvb" },
 55            { "Eintracht Frankfurt", "sge" },
 56            { "FC Augsburg", "fca" },
 57            { "FC Bayern München", "fcb" },
 58            { "FC St. Pauli", "fcs" },
 59            { "FSV Mainz 05", "m05" },
 60            { "Hamburger SV", "hsv" },
 61            { "RB Leipzig", "rbl" },
 62            { "SC Freiburg", "scf" },
 63            { "VfB Stuttgart", "vfb" },
 64            { "VfL Wolfsburg", "wob" },
 65            { "Werder Bremen", "svw" }
 66        };
 67
 68    public static MatchContextDocumentSelection ForMatch(
 69        string homeTeam,
 70        string awayTeam,
 71        string communityContext,
 72        string? competition = null)
 73    {
 74        ArgumentException.ThrowIfNullOrWhiteSpace(homeTeam);
 75        ArgumentException.ThrowIfNullOrWhiteSpace(awayTeam);
 76        ArgumentException.ThrowIfNullOrWhiteSpace(communityContext);
 77
 78        var homeAbbreviation = GetTeamAbbreviation(homeTeam);
 79        var awayAbbreviation = GetTeamAbbreviation(awayTeam);
 80        var standingsDocumentName = GetStandingsDocumentName(competition);
 81        var policy = ResolvePolicy(communityContext, competition);
 82
 83        var requiredDocuments = new List<string> { standingsDocumentName };
 84        if (policy.IncludeCommunityRules)
 85        {
 86            requiredDocuments.Add($"community-rules-{communityContext}.md");
 87        }
 88
 89        if (policy.IncludeRecentHistory)
 90        {
 91            requiredDocuments.Add($"recent-history-{homeAbbreviation}.csv");
 92            requiredDocuments.Add($"recent-history-{awayAbbreviation}.csv");
 93        }
 94
 95        if (policy.IncludeFifaRankings)
 96        {
 97            requiredDocuments.Add(GetFifaRankingDocumentName(homeTeam));
 98            requiredDocuments.Add(GetFifaRankingDocumentName(awayTeam));
 99        }
 100
 101        if (policy.IncludeLineups)
 102        {
 103            requiredDocuments.Add(GetLineupDocumentName(homeTeam));
 104            requiredDocuments.Add(GetLineupDocumentName(awayTeam));
 105        }
 106
 107        if (policy.IncludeHomeAwayHistory)
 108        {
 109            requiredDocuments.Add($"home-history-{homeAbbreviation}.csv");
 110            requiredDocuments.Add($"away-history-{awayAbbreviation}.csv");
 111        }
 112
 113        if (policy.IncludeHeadToHead)
 114        {
 115            requiredDocuments.Add($"head-to-head-{homeAbbreviation}-vs-{awayAbbreviation}.csv");
 116        }
 117
 118        var optionalDocuments = policy.IncludeTransfers
 119            ? new List<string>
 120            {
 121                $"{homeAbbreviation}-transfers.csv",
 122                $"{awayAbbreviation}-transfers.csv"
 123            }
 124            : [];
 125
 126        return new MatchContextDocumentSelection(requiredDocuments, optionalDocuments);
 127    }
 128
 129    public static MatchContextDocumentSelection ForCommunity(
 130        string communityContext,
 131        string? competition = null)
 132    {
 133        ArgumentException.ThrowIfNullOrWhiteSpace(communityContext);
 134
 135        var standingsDocumentName = GetStandingsDocumentName(competition);
 136        var policy = ResolvePolicy(communityContext, competition);
 137        var requiredDocuments = new List<string> { standingsDocumentName };
 138
 139        if (policy.IncludeCommunityRules)
 140        {
 141            requiredDocuments.Add($"community-rules-{communityContext}.md");
 142        }
 143
 144        return new MatchContextDocumentSelection(requiredDocuments, []);
 145    }
 146
 147    public static string GetStandingsDocumentName(string? competition = null)
 148    {
 149        return string.Equals(competition, CompetitionIds.FifaWorldCup2026, StringComparison.OrdinalIgnoreCase)
 150            ? "fifa-world-cup-2026-standings.csv"
 151            : "bundesliga-standings.csv";
 152    }
 153
 154    public static string GetStandingsDocumentBaseName(string? competition = null)
 155    {
 156        var documentName = GetStandingsDocumentName(competition);
 157        return documentName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)
 158            ? documentName[..^4]
 159            : documentName;
 160    }
 161
 162    public static string GetFifaRankingDocumentName(string teamName)
 163    {
 164        ArgumentException.ThrowIfNullOrWhiteSpace(teamName);
 165
 166        return $"fifa-ranking-{GetTeamAbbreviation(teamName)}.csv";
 167    }
 168
 169    public static string GetLineupDocumentName(string teamName)
 170    {
 171        ArgumentException.ThrowIfNullOrWhiteSpace(teamName);
 172
 173        return $"lineup-{GetTeamAbbreviation(teamName)}.csv";
 174    }
 175
 176    public static string GetTeamAbbreviation(string teamName)
 177    {
 178        ArgumentException.ThrowIfNullOrWhiteSpace(teamName);
 179
 180        if (TeamAbbreviations.TryGetValue(teamName, out var abbreviation))
 181        {
 182            return abbreviation;
 183        }
 184
 185        return SlugifyTeamName(teamName);
 186    }
 187
 188    private static MatchContextDocumentPolicy ResolvePolicy(string communityContext, string? competition)
 189    {
 190        if (CommunityPolicies.TryGetValue(communityContext, out var communityPolicy))
 191        {
 192            return communityPolicy;
 193        }
 194
 195        return string.Equals(competition, CompetitionIds.FifaWorldCup2026, StringComparison.OrdinalIgnoreCase)
 196            ? WorldCup2026Policy
 197            : BundesligaPolicy;
 198    }
 199
 200    private static string SlugifyTeamName(string teamName)
 201    {
 202        var normalized = teamName.Normalize(NormalizationForm.FormD);
 203        var builder = new StringBuilder();
 204
 205        foreach (var character in normalized)
 206        {
 207            var category = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(character);
 208            if (category == System.Globalization.UnicodeCategory.NonSpacingMark)
 209            {
 210                continue;
 211            }
 212
 213            if (char.IsLetterOrDigit(character))
 214            {
 215                builder.Append(char.ToLowerInvariant(character));
 216            }
 217            else if (builder.Length > 0 && builder[^1] != '-')
 218            {
 219                builder.Append('-');
 220            }
 221        }
 222
 223        var slug = Regex.Replace(builder.ToString().Trim('-'), "-{2,}", "-");
 224        return string.IsNullOrWhiteSpace(slug) ? "unknown" : slug;
 225    }
 226}