< Summary

Information
Class: FirebaseAdapter.FirebaseKpiContextProvider
Assembly: FirebaseAdapter
File(s): /home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseKpiContextProvider.cs
Line coverage
91%
Covered lines: 64
Uncovered lines: 6
Coverable lines: 70
Total lines: 207
Line coverage: 91.4%
Branch coverage
94%
Covered branches: 53
Total branches: 56
Branch coverage: 94.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%44100%
GetContextAsync()75%4476.92%
GetBonusQuestionContextByCommunityAsync()50%22100%
GetBonusQuestionContextAsync()93.75%1616100%
IsAlwaysIncludedBonusDocument(...)100%22100%
IsTopScorerTeamQuestion(...)100%11100%
IsTrainerChangeQuestion(...)100%1414100%
IsRelegationQuestion(...)100%1212100%
GetKpiDocumentContextAsync()100%2276.92%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseKpiContextProvider.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using EHonda.KicktippAi.Core;
 3using Microsoft.Extensions.Logging;
 4
 5namespace FirebaseAdapter;
 6
 7/// <summary>
 8/// Firebase-based context provider for KPI documents.
 9/// Retrieves KPI documents from Firestore for use in bonus predictions.
 10/// </summary>
 11public class FirebaseKpiContextProvider : IKpiContextProvider
 12{
 13    private const string FifaRankingsDocumentName = "fifa-rankings";
 14    private const string LineupsDocumentName = "lineups";
 15    private const string TopScorerTeamQuestion = "Welche Mannschaft stellt den Spieler mit den meisten Toren?";
 16
 17    private readonly IKpiRepository _kpiRepository;
 18    private readonly ILogger<FirebaseKpiContextProvider> _logger;
 19
 120    public FirebaseKpiContextProvider(IKpiRepository kpiRepository, ILogger<FirebaseKpiContextProvider> logger)
 21    {
 122        _kpiRepository = kpiRepository ?? throw new ArgumentNullException(nameof(kpiRepository));
 123        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 124    }
 25
 26    /// <summary>
 27    /// Gets all KPI documents as context for predictions for a specific community.
 28    /// This method provides all available KPI documents for bonus predictions.
 29    /// </summary>
 30    /// <param name="communityContext">The community context to filter by.</param>
 31    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 32    /// <returns>An async enumerable of document contexts containing KPI data.</returns>
 33    public async IAsyncEnumerable<DocumentContext> GetContextAsync(string communityContext, [EnumeratorCancellation] Can
 34    {
 135        _logger.LogDebug("Retrieving all KPI documents for context in community: {CommunityContext}", communityContext);
 36
 37        IReadOnlyList<KpiDocument> kpiDocuments;
 38        try
 39        {
 140            kpiDocuments = await _kpiRepository.GetAllKpiDocumentsAsync(communityContext, cancellationToken);
 141            _logger.LogInformation("Found {DocumentCount} KPI documents for context in community: {CommunityContext}", k
 142        }
 043        catch (Exception ex)
 44        {
 045            _logger.LogError(ex, "Failed to retrieve KPI documents for context in community: {CommunityContext}", commun
 046            throw;
 47        }
 48
 149        foreach (var kpiDocument in kpiDocuments)
 50        {
 151            _logger.LogDebug("Providing KPI document context: {DocumentName}", kpiDocument.DocumentName);
 52
 153            yield return new DocumentContext(
 154                Name: kpiDocument.DocumentName,
 155                Content: kpiDocument.Content);
 56        }
 157    }
 58
 59    /// <summary>
 60    /// Gets KPI context specifically for bonus questions for a specific community.
 61    /// This is an alias for GetContextAsync() since KPI documents are primarily used for bonus predictions.
 62    /// </summary>
 63    /// <param name="communityContext">The community context to filter by.</param>
 64    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 65    /// <returns>An async enumerable of document contexts containing KPI data for bonus questions.</returns>
 66    public async IAsyncEnumerable<DocumentContext> GetBonusQuestionContextByCommunityAsync(string communityContext, [Enu
 67    {
 168        await foreach (var context in GetContextAsync(communityContext, cancellationToken))
 69        {
 170            yield return context;
 71        }
 172    }
 73
 74    /// <summary>
 75    /// Gets KPI context specifically tailored for a bonus question based on its content and community.
 76    /// This method provides targeted context by including additional relevant documents based on question patterns.
 77    /// </summary>
 78    /// <param name="questionText">The text of the bonus question to provide context for.</param>
 79    /// <param name="communityContext">The community context to filter by.</param>
 80    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 81    /// <returns>An async enumerable of document contexts containing relevant KPI data for the specific question.</retur
 82    public async IAsyncEnumerable<DocumentContext> GetBonusQuestionContextAsync(string questionText, string communityCon
 83    {
 184        _logger.LogDebug("Retrieving targeted KPI context for question: {QuestionText} in community: {CommunityContext}"
 85
 86        // For now, we'll get all documents for the community and filter based on question patterns
 87        // In the future, we could make GetKpiDocumentContextAsync community-aware too
 88
 89        // Always include team data for all bonus questions
 190        await foreach (var context in GetContextAsync(communityContext, cancellationToken))
 91        {
 192            if (IsAlwaysIncludedBonusDocument(context.Name))
 93            {
 194                yield return context;
 95            }
 196            else if (IsTopScorerTeamQuestion(questionText) &&
 197                     string.Equals(context.Name, LineupsDocumentName, StringComparison.OrdinalIgnoreCase))
 98            {
 199                _logger.LogDebug("Detected WM26 top scorer team question, including lineups data");
 1100                yield return context;
 101            }
 102
 103            // For trainer/manager change questions, also include manager data
 1104            else if (IsTrainerChangeQuestion(questionText) && context.Name.Contains("manager-data", StringComparison.Ord
 105            {
 1106                _logger.LogDebug("Detected trainer/manager change question, including manager data");
 1107                yield return context;
 108            }
 109
 110            // For relegation questions, also include manager data
 1111            else if (IsRelegationQuestion(questionText) && context.Name.Contains("manager-data", StringComparison.Ordina
 112            {
 1113                _logger.LogDebug("Detected relegation question, including manager data");
 1114                yield return context;
 115            }
 116        }
 1117    }
 118
 119    private static bool IsAlwaysIncludedBonusDocument(string documentName)
 120    {
 1121        return documentName.Contains("team-data", StringComparison.OrdinalIgnoreCase) ||
 1122               string.Equals(documentName, FifaRankingsDocumentName, StringComparison.OrdinalIgnoreCase);
 123    }
 124
 125    private static bool IsTopScorerTeamQuestion(string questionText)
 126    {
 1127        return string.Equals(questionText, TopScorerTeamQuestion, StringComparison.Ordinal);
 128    }
 129
 130    /// <summary>
 131    /// Determines if a bonus question is about trainer/manager changes based on its text.
 132    /// </summary>
 133    /// <param name="questionText">The text of the bonus question.</param>
 134    /// <returns>True if the question is about trainer/manager changes, false otherwise.</returns>
 135    private static bool IsTrainerChangeQuestion(string questionText)
 136    {
 1137        if (string.IsNullOrWhiteSpace(questionText))
 1138            return false;
 139
 1140        var lowerText = questionText.ToLowerInvariant();
 141
 142        // Check for German trainer/manager change keywords
 1143        return lowerText.Contains("trainerwechsel") ||
 1144               lowerText.Contains("trainer") ||
 1145               lowerText.Contains("cheftrainer") ||
 1146               lowerText.Contains("entlassung") ||
 1147               lowerText.Contains("entlassen") ||
 1148               lowerText.Contains("manager") ||
 1149               lowerText.Contains("coach");
 150    }
 151
 152    /// <summary>
 153    /// Determines if a bonus question is about relegation based on its text.
 154    /// </summary>
 155    /// <param name="questionText">The text of the bonus question.</param>
 156    /// <returns>True if the question is about relegation, false otherwise.</returns>
 157    private static bool IsRelegationQuestion(string questionText)
 158    {
 1159        if (string.IsNullOrWhiteSpace(questionText))
 1160            return false;
 161
 1162        var lowerText = questionText.ToLowerInvariant();
 163
 164        // Check for German relegation keywords
 1165        return lowerText.Contains("16-18") ||
 1166               lowerText.Contains("plätze 16-18") ||
 1167               lowerText.Contains("abstieg") ||
 1168               lowerText.Contains("relegation") ||
 1169               lowerText.Contains("abstiegsplätze") ||
 1170               lowerText.Contains("absteiger");
 171    }
 172
 173    /// <summary>
 174    /// Gets a specific KPI document by its ID.
 175    /// </summary>
 176    /// <param name="documentId">The ID of the KPI document to retrieve.</param>
 177    /// <param name="communityContext">The community context to filter by.</param>
 178    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 179    /// <returns>The document context for the specified KPI document, or null if not found.</returns>
 180    public async Task<DocumentContext?> GetKpiDocumentContextAsync(string documentId, string communityContext, Cancellat
 181    {
 1182        _logger.LogDebug("Retrieving specific KPI document: {DocumentId} for community: {CommunityContext}", documentId,
 183
 184        try
 185        {
 1186            var kpiDocument = await _kpiRepository.GetKpiDocumentAsync(documentId, communityContext, cancellationToken);
 187
 1188            if (kpiDocument == null)
 189            {
 1190                _logger.LogWarning("KPI document not found: {DocumentId} for community: {CommunityContext}", documentId,
 1191                return null;
 192            }
 193
 1194            _logger.LogDebug("Found KPI document: {DocumentId} for community: {CommunityContext}", documentId, community
 195
 1196            return new DocumentContext(
 1197                Name: kpiDocument.DocumentName,
 1198                Content: kpiDocument.Content);
 199        }
 0200        catch (Exception ex)
 201        {
 0202            _logger.LogError(ex, "Failed to retrieve KPI document: {DocumentId} for community: {CommunityContext}", docu
 0203            throw;
 204        }
 1205    }
 206
 207}