< Summary

Information
Class: ContextProviders.Kicktipp.KicktippContextProvider
Assembly: ContextProviders.Kicktipp
File(s): /home/runner/work/KicktippAi/KicktippAi/src/ContextProviders.Kicktipp/KicktippContextProvider.cs
Line coverage
100%
Covered lines: 132
Uncovered lines: 0
Coverable lines: 132
Total lines: 303
Line coverage: 100%
Branch coverage
88%
Covered branches: 55
Total branches: 62
Branch coverage: 88.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%1010100%
LoadTeamHistoryAsync()100%22100%
LoadHomeAwayHistoryAsync()100%22100%
LoadDetailedHeadToHeadHistoryAsync()100%22100%
GetMatchesWithHistoryAsync()50%22100%
GetContextAsync()87.5%88100%
GetMatchContextAsync()94.44%1818100%
GetBonusQuestionContextAsync()87.5%88100%
CurrentStandings()100%11100%
CurrentBundesligaStandings()100%11100%
RecentHistory()100%22100%
HomeHistory()50%22100%
AwayHistory()50%22100%
HeadToHeadHistory()50%22100%
CommunityScoringRules()100%22100%
ReadFileContentAsync()100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/ContextProviders.Kicktipp/KicktippContextProvider.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using ContextProviders.Kicktipp.Csv;
 3using EHonda.KicktippAi.Core;
 4using KicktippIntegration;
 5using Microsoft.Extensions.FileProviders;
 6
 7namespace ContextProviders.Kicktipp;
 8
 9public class KicktippContextProvider : IKicktippContextProvider
 10{
 11    private readonly IKicktippClient _kicktippClient;
 12    private readonly IFileProvider _communityRulesFileProvider;
 13    private readonly string _community;
 14    private readonly string _communityContext;
 15    private readonly string _competition;
 16    private readonly int? _matchday;
 17    private readonly Lazy<Task<IReadOnlyDictionary<string, List<MatchResult>>>> _teamHistoryLazy;
 18    private readonly Lazy<Task<IReadOnlyDictionary<string, (List<MatchResult> homeHistory, List<MatchResult> awayHistory
 19    private readonly Lazy<Task<IReadOnlyDictionary<string, List<HeadToHeadResult>>>> _detailedHeadToHeadHistoryLazy;
 20
 121    public KicktippContextProvider(
 122        IKicktippClient kicktippClient,
 123        IFileProvider communityRulesFileProvider,
 124        string community,
 125        string? communityContext = null,
 126        string? competition = null,
 127        int? matchday = null)
 28    {
 129        _kicktippClient = kicktippClient ?? throw new ArgumentNullException(nameof(kicktippClient));
 130        _communityRulesFileProvider = communityRulesFileProvider ?? throw new ArgumentNullException(nameof(communityRule
 131        _community = community ?? throw new ArgumentNullException(nameof(community));
 132        _communityContext = communityContext ?? community;
 133        _competition = competition ?? CompetitionIds.Bundesliga2025_26;
 134        _matchday = matchday;
 135        _teamHistoryLazy = new Lazy<Task<IReadOnlyDictionary<string, List<MatchResult>>>>(LoadTeamHistoryAsync);
 136        _homeAwayHistoryLazy = new Lazy<Task<IReadOnlyDictionary<string, (List<MatchResult> homeHistory, List<MatchResul
 137        _detailedHeadToHeadHistoryLazy = new Lazy<Task<IReadOnlyDictionary<string, List<HeadToHeadResult>>>>(LoadDetaile
 138    }
 39
 40    private async Task<IReadOnlyDictionary<string, List<MatchResult>>> LoadTeamHistoryAsync()
 41    {
 142        var matchesWithHistory = await GetMatchesWithHistoryAsync();
 143        var teamHistory = new Dictionary<string, List<MatchResult>>();
 44
 145        foreach (var matchWithHistory in matchesWithHistory)
 46        {
 147            teamHistory[matchWithHistory.Match.HomeTeam] = matchWithHistory.HomeTeamHistory;
 148            teamHistory[matchWithHistory.Match.AwayTeam] = matchWithHistory.AwayTeamHistory;
 49        }
 50
 151        return teamHistory;
 152    }
 53
 54    private async Task<IReadOnlyDictionary<string, (List<MatchResult> homeHistory, List<MatchResult> awayHistory)>> Load
 55    {
 156        var matchesWithHistory = await GetMatchesWithHistoryAsync();
 157        var homeAwayHistory = new Dictionary<string, (List<MatchResult> homeHistory, List<MatchResult> awayHistory)>();
 58
 159        foreach (var matchWithHistory in matchesWithHistory)
 60        {
 161            var homeTeam = matchWithHistory.Match.HomeTeam;
 162            var awayTeam = matchWithHistory.Match.AwayTeam;
 63
 164            var (homeTeamHistory, awayTeamHistory) = await _kicktippClient.GetHomeAwayHistoryAsync(_community, homeTeam,
 165            var cacheKey = $"{homeTeam}|{awayTeam}";
 166            homeAwayHistory[cacheKey] = (homeTeamHistory, awayTeamHistory);
 167        }
 68
 169        return homeAwayHistory;
 170    }
 71
 72    private async Task<IReadOnlyDictionary<string, List<HeadToHeadResult>>> LoadDetailedHeadToHeadHistoryAsync()
 73    {
 174        var matchesWithHistory = await GetMatchesWithHistoryAsync();
 175        var detailedHeadToHeadHistory = new Dictionary<string, List<HeadToHeadResult>>();
 76
 177        foreach (var matchWithHistory in matchesWithHistory)
 78        {
 179            var homeTeam = matchWithHistory.Match.HomeTeam;
 180            var awayTeam = matchWithHistory.Match.AwayTeam;
 81
 182            var history = await _kicktippClient.GetHeadToHeadDetailedHistoryAsync(_community, homeTeam, awayTeam);
 183            var cacheKey = $"{homeTeam}|{awayTeam}";
 184            detailedHeadToHeadHistory[cacheKey] = history;
 185        }
 86
 187        return detailedHeadToHeadHistory;
 188    }
 89
 90    private Task<List<MatchWithHistory>> GetMatchesWithHistoryAsync()
 91    {
 192        return _matchday.HasValue
 193            ? _kicktippClient.GetMatchesWithHistoryAsync(_community, _matchday.Value)
 194            : _kicktippClient.GetMatchesWithHistoryAsync(_community);
 95    }
 96
 97    public async IAsyncEnumerable<DocumentContext> GetContextAsync([EnumeratorCancellation] CancellationToken cancellati
 98    {
 199        var selection = MatchContextDocumentCatalog.ForCommunity(_communityContext, _competition);
 1100        foreach (var documentName in selection.RequiredDocumentNames)
 101        {
 1102            if (documentName == MatchContextDocumentCatalog.GetStandingsDocumentName(_competition))
 103            {
 1104                yield return await CurrentStandings();
 105            }
 1106            else if (documentName == $"community-rules-{_communityContext}.md")
 107            {
 1108                yield return await CommunityScoringRules();
 109            }
 110        }
 1111    }
 112
 113    /// <summary>
 114    /// Gets context for the two teams in a match.
 115    /// </summary>
 116    /// <param name="homeTeam">The home team name.</param>
 117    /// <param name="awayTeam">The away team name.</param>
 118    /// <returns>An enumerable of context documents for both teams.</returns>
 119    public async IAsyncEnumerable<DocumentContext> GetMatchContextAsync(string homeTeam, string awayTeam, [EnumeratorCan
 120    {
 1121        var selection = MatchContextDocumentCatalog.ForMatch(homeTeam, awayTeam, _communityContext, _competition);
 1122        var homeAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(homeTeam);
 1123        var awayAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(awayTeam);
 124
 1125        foreach (var documentName in selection.RequiredDocumentNames)
 126        {
 1127            if (documentName == MatchContextDocumentCatalog.GetStandingsDocumentName(_competition))
 128            {
 1129                yield return await CurrentStandings();
 130            }
 1131            else if (documentName == $"community-rules-{_communityContext}.md")
 132            {
 1133                yield return await CommunityScoringRules();
 134            }
 1135            else if (documentName == $"recent-history-{homeAbbreviation}.csv")
 136            {
 1137                yield return await RecentHistory(homeTeam);
 138            }
 1139            else if (documentName == $"recent-history-{awayAbbreviation}.csv")
 140            {
 1141                yield return await RecentHistory(awayTeam);
 142            }
 1143            else if (documentName == $"home-history-{homeAbbreviation}.csv")
 144            {
 1145                yield return await HomeHistory(homeTeam, awayTeam);
 146            }
 1147            else if (documentName == $"away-history-{awayAbbreviation}.csv")
 148            {
 1149                yield return await AwayHistory(homeTeam, awayTeam);
 150            }
 1151            else if (documentName == $"head-to-head-{homeAbbreviation}-vs-{awayAbbreviation}.csv")
 152            {
 1153                yield return await HeadToHeadHistory(homeTeam, awayTeam);
 154            }
 155        }
 1156    }
 157
 158    /// <summary>
 159    /// Gets context for bonus questions.
 160    /// </summary>
 161    /// <returns>An enumerable of context documents relevant for bonus questions.</returns>
 162    public async IAsyncEnumerable<DocumentContext> GetBonusQuestionContextAsync([EnumeratorCancellation] CancellationTok
 163    {
 1164        var selection = MatchContextDocumentCatalog.ForCommunity(_communityContext, _competition);
 1165        foreach (var documentName in selection.RequiredDocumentNames)
 166        {
 1167            if (documentName == MatchContextDocumentCatalog.GetStandingsDocumentName(_competition))
 168            {
 1169                yield return await CurrentStandings();
 170            }
 1171            else if (documentName == $"community-rules-{_communityContext}.md")
 172            {
 1173                yield return await CommunityScoringRules();
 174            }
 175        }
 1176    }
 177
 178    /// <summary>
 179    /// Gets the current competition standings as context.
 180    /// </summary>
 181    /// <returns>A document context containing the current standings.</returns>
 182    public async Task<DocumentContext> CurrentStandings()
 183    {
 1184        var standings = await _kicktippClient.GetStandingsAsync(_community);
 185
 1186        return standings.ToCsvDocumentContext<TeamStanding, TeamStandingCsvMap>(
 1187            MatchContextDocumentCatalog.GetStandingsDocumentBaseName(_competition));
 1188    }
 189
 190    /// <summary>
 191    /// Gets the current Bundesliga standings as context.
 192    /// </summary>
 193    /// <returns>A document context containing the current standings.</returns>
 194    public Task<DocumentContext> CurrentBundesligaStandings()
 195    {
 1196        return CurrentStandings();
 197    }
 198
 199    /// <summary>
 200    /// Gets recent match history for a specific team.
 201    /// </summary>
 202    /// <param name="teamName">The name of the team to get history for.</param>
 203    /// <returns>A document context containing the team's recent match history.</returns>
 204    public async Task<DocumentContext> RecentHistory(string teamName)
 205    {
 1206        var teamHistory = await _teamHistoryLazy.Value;
 1207        var matchResults = teamHistory.TryGetValue(teamName, out var results) ? results : new List<MatchResult>();
 208
 1209        var teamAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(teamName);
 210
 1211        return matchResults.ToCsvDocumentContext<MatchResult, MatchResultCsvMap>($"recent-history-{teamAbbreviation}");
 1212    }
 213
 214    /// <summary>
 215    /// Gets home/away specific history for both teams in a match.
 216    /// </summary>
 217    /// <param name="homeTeam">The home team name.</param>
 218    /// <param name="awayTeam">The away team name.</param>
 219    /// <returns>A document context containing home team's home history and away team's away history.</returns>
 220    public async Task<DocumentContext> HomeHistory(string homeTeam, string awayTeam)
 221    {
 1222        var homeAwayHistory = await _homeAwayHistoryLazy.Value;
 1223        var cacheKey = $"{homeTeam}|{awayTeam}";
 224
 1225        var (homeTeamHistory, _) = homeAwayHistory.TryGetValue(cacheKey, out var history)
 1226            ? history
 1227            : (new List<MatchResult>(), new List<MatchResult>());
 228
 1229        var homeAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(homeTeam);
 230
 1231        return homeTeamHistory.ToCsvDocumentContext<MatchResult, MatchResultCsvMap>($"home-history-{homeAbbreviation}");
 1232    }
 233
 234    public async Task<DocumentContext> AwayHistory(string homeTeam, string awayTeam)
 235    {
 1236        var homeAwayHistory = await _homeAwayHistoryLazy.Value;
 1237        var cacheKey = $"{homeTeam}|{awayTeam}";
 238
 1239        var (_, awayTeamHistory) = homeAwayHistory.TryGetValue(cacheKey, out var history)
 1240            ? history
 1241            : (new List<MatchResult>(), new List<MatchResult>());
 242
 1243        var awayAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(awayTeam);
 244
 1245        return awayTeamHistory.ToCsvDocumentContext<MatchResult, MatchResultCsvMap>($"away-history-{awayAbbreviation}");
 1246    }
 247
 248    /// <summary>
 249    /// Gets head-to-head history between two teams.
 250    /// </summary>
 251    /// <param name="homeTeam">The home team name.</param>
 252    /// <param name="awayTeam">The away team name.</param>
 253    /// <returns>A document context containing head-to-head match history.</returns>
 254    public async Task<DocumentContext> HeadToHeadHistory(string homeTeam, string awayTeam)
 255    {
 1256        var detailedHeadToHeadHistory = await _detailedHeadToHeadHistoryLazy.Value;
 1257        var cacheKey = $"{homeTeam}|{awayTeam}";
 258
 1259        var history = detailedHeadToHeadHistory.TryGetValue(cacheKey, out var cachedHistory)
 1260            ? cachedHistory
 1261            : new List<HeadToHeadResult>();
 262
 1263        var homeAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(homeTeam);
 1264        var awayAbbreviation = MatchContextDocumentCatalog.GetTeamAbbreviation(awayTeam);
 265
 1266        return history.ToCsvDocumentContext<HeadToHeadResult, HeadToHeadResultCsvMap>(
 1267            $"head-to-head-{homeAbbreviation}-vs-{awayAbbreviation}");
 1268    }
 269
 270    /// <summary>
 271    /// Gets the community scoring rules as context.
 272    /// </summary>
 273    /// <returns>A document context containing the scoring rules.</returns>
 274    /// <exception cref="FileNotFoundException">Thrown when the community-specific scoring rules file is not found.</exc
 275    public async Task<DocumentContext> CommunityScoringRules()
 276    {
 1277        var filePath = $"{_communityContext}.md";
 1278        var fileInfo = _communityRulesFileProvider.GetFileInfo(filePath);
 279
 1280        if (!fileInfo.Exists)
 281        {
 1282            throw new FileNotFoundException(
 1283                $"Community scoring rules file not found: {filePath}. Expected file for community context '{_communityCo
 284        }
 285
 1286        var content = await ReadFileContentAsync(fileInfo);
 1287        return new DocumentContext(
 1288            Name: $"community-rules-{_communityContext}.md",
 1289            Content: content);
 1290    }
 291
 292    /// <summary>
 293    /// Reads the content from a file info asynchronously.
 294    /// </summary>
 295    /// <param name="fileInfo">The file info to read from.</param>
 296    /// <returns>The file content as a string.</returns>
 297    private static async Task<string> ReadFileContentAsync(IFileInfo fileInfo)
 298    {
 1299        await using var stream = fileInfo.CreateReadStream();
 1300        using var reader = new StreamReader(stream);
 1301        return await reader.ReadToEndAsync();
 1302    }
 303}