< Summary

Information
Class: FirebaseAdapter.FirebaseKpiContextProvider
Assembly: FirebaseAdapter
File(s): /home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseKpiContextProvider.cs
Line coverage
90%
Covered lines: 57
Uncovered lines: 6
Coverable lines: 63
Total lines: 187
Line coverage: 90.4%
Branch coverage
94%
Covered branches: 47
Total branches: 50
Branch coverage: 94%
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()91.67%1212100%
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 readonly IKpiRepository _kpiRepository;
 14    private readonly ILogger<FirebaseKpiContextProvider> _logger;
 15
 116    public FirebaseKpiContextProvider(IKpiRepository kpiRepository, ILogger<FirebaseKpiContextProvider> logger)
 17    {
 118        _kpiRepository = kpiRepository ?? throw new ArgumentNullException(nameof(kpiRepository));
 119        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 120    }
 21
 22    /// <summary>
 23    /// Gets all KPI documents as context for predictions for a specific community.
 24    /// This method provides all available KPI documents for bonus predictions.
 25    /// </summary>
 26    /// <param name="communityContext">The community context to filter by.</param>
 27    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 28    /// <returns>An async enumerable of document contexts containing KPI data.</returns>
 29    public async IAsyncEnumerable<DocumentContext> GetContextAsync(string communityContext, [EnumeratorCancellation] Can
 30    {
 131        _logger.LogDebug("Retrieving all KPI documents for context in community: {CommunityContext}", communityContext);
 32
 33        IReadOnlyList<KpiDocument> kpiDocuments;
 34        try
 35        {
 136            kpiDocuments = await _kpiRepository.GetAllKpiDocumentsAsync(communityContext, cancellationToken);
 137            _logger.LogInformation("Found {DocumentCount} KPI documents for context in community: {CommunityContext}", k
 138        }
 039        catch (Exception ex)
 40        {
 041            _logger.LogError(ex, "Failed to retrieve KPI documents for context in community: {CommunityContext}", commun
 042            throw;
 43        }
 44
 145        foreach (var kpiDocument in kpiDocuments)
 46        {
 147            _logger.LogDebug("Providing KPI document context: {DocumentName}", kpiDocument.DocumentName);
 48
 149            yield return new DocumentContext(
 150                Name: kpiDocument.DocumentName,
 151                Content: kpiDocument.Content);
 52        }
 153    }
 54
 55    /// <summary>
 56    /// Gets KPI context specifically for bonus questions for a specific community.
 57    /// This is an alias for GetContextAsync() since KPI documents are primarily used for bonus predictions.
 58    /// </summary>
 59    /// <param name="communityContext">The community context to filter by.</param>
 60    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 61    /// <returns>An async enumerable of document contexts containing KPI data for bonus questions.</returns>
 62    public async IAsyncEnumerable<DocumentContext> GetBonusQuestionContextByCommunityAsync(string communityContext, [Enu
 63    {
 164        await foreach (var context in GetContextAsync(communityContext, cancellationToken))
 65        {
 166            yield return context;
 67        }
 168    }
 69
 70    /// <summary>
 71    /// Gets KPI context specifically tailored for a bonus question based on its content and community.
 72    /// This method provides targeted context by including additional relevant documents based on question patterns.
 73    /// </summary>
 74    /// <param name="questionText">The text of the bonus question to provide context for.</param>
 75    /// <param name="communityContext">The community context to filter by.</param>
 76    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 77    /// <returns>An async enumerable of document contexts containing relevant KPI data for the specific question.</retur
 78    public async IAsyncEnumerable<DocumentContext> GetBonusQuestionContextAsync(string questionText, string communityCon
 79    {
 180        _logger.LogDebug("Retrieving targeted KPI context for question: {QuestionText} in community: {CommunityContext}"
 81
 82        // For now, we'll get all documents for the community and filter based on question patterns
 83        // In the future, we could make GetKpiDocumentContextAsync community-aware too
 84
 85        // Always include team data for all bonus questions
 186        await foreach (var context in GetContextAsync(communityContext, cancellationToken))
 87        {
 88            // Filter for team-data document
 189            if (context.Name.Contains("team-data", StringComparison.OrdinalIgnoreCase))
 90            {
 191                yield return context;
 92            }
 93
 94            // For trainer/manager change questions, also include manager data
 195            else if (IsTrainerChangeQuestion(questionText) && context.Name.Contains("manager-data", StringComparison.Ord
 96            {
 197                _logger.LogDebug("Detected trainer/manager change question, including manager data");
 198                yield return context;
 99            }
 100
 101            // For relegation questions, also include manager data
 1102            else if (IsRelegationQuestion(questionText) && context.Name.Contains("manager-data", StringComparison.Ordina
 103            {
 1104                _logger.LogDebug("Detected relegation question, including manager data");
 1105                yield return context;
 106            }
 107        }
 1108    }
 109
 110    /// <summary>
 111    /// Determines if a bonus question is about trainer/manager changes based on its text.
 112    /// </summary>
 113    /// <param name="questionText">The text of the bonus question.</param>
 114    /// <returns>True if the question is about trainer/manager changes, false otherwise.</returns>
 115    private static bool IsTrainerChangeQuestion(string questionText)
 116    {
 1117        if (string.IsNullOrWhiteSpace(questionText))
 1118            return false;
 119
 1120        var lowerText = questionText.ToLowerInvariant();
 121
 122        // Check for German trainer/manager change keywords
 1123        return lowerText.Contains("trainerwechsel") ||
 1124               lowerText.Contains("trainer") ||
 1125               lowerText.Contains("cheftrainer") ||
 1126               lowerText.Contains("entlassung") ||
 1127               lowerText.Contains("entlassen") ||
 1128               lowerText.Contains("manager") ||
 1129               lowerText.Contains("coach");
 130    }
 131
 132    /// <summary>
 133    /// Determines if a bonus question is about relegation based on its text.
 134    /// </summary>
 135    /// <param name="questionText">The text of the bonus question.</param>
 136    /// <returns>True if the question is about relegation, false otherwise.</returns>
 137    private static bool IsRelegationQuestion(string questionText)
 138    {
 1139        if (string.IsNullOrWhiteSpace(questionText))
 1140            return false;
 141
 1142        var lowerText = questionText.ToLowerInvariant();
 143
 144        // Check for German relegation keywords
 1145        return lowerText.Contains("16-18") ||
 1146               lowerText.Contains("plätze 16-18") ||
 1147               lowerText.Contains("abstieg") ||
 1148               lowerText.Contains("relegation") ||
 1149               lowerText.Contains("abstiegsplätze") ||
 1150               lowerText.Contains("absteiger");
 151    }
 152
 153    /// <summary>
 154    /// Gets a specific KPI document by its ID.
 155    /// </summary>
 156    /// <param name="documentId">The ID of the KPI document to retrieve.</param>
 157    /// <param name="communityContext">The community context to filter by.</param>
 158    /// <param name="cancellationToken">Cancellation token for the operation.</param>
 159    /// <returns>The document context for the specified KPI document, or null if not found.</returns>
 160    public async Task<DocumentContext?> GetKpiDocumentContextAsync(string documentId, string communityContext, Cancellat
 161    {
 1162        _logger.LogDebug("Retrieving specific KPI document: {DocumentId} for community: {CommunityContext}", documentId,
 163
 164        try
 165        {
 1166            var kpiDocument = await _kpiRepository.GetKpiDocumentAsync(documentId, communityContext, cancellationToken);
 167
 1168            if (kpiDocument == null)
 169            {
 1170                _logger.LogWarning("KPI document not found: {DocumentId} for community: {CommunityContext}", documentId,
 1171                return null;
 172            }
 173
 1174            _logger.LogDebug("Found KPI document: {DocumentId} for community: {CommunityContext}", documentId, community
 175
 1176            return new DocumentContext(
 1177                Name: kpiDocument.DocumentName,
 1178                Content: kpiDocument.Content);
 179        }
 0180        catch (Exception ex)
 181        {
 0182            _logger.LogError(ex, "Failed to retrieve KPI document: {DocumentId} for community: {CommunityContext}", docu
 0183            throw;
 184        }
 1185    }
 186
 187}