< Summary

Information
Class: Orchestrator.Commands.Operations.CollectContext.CollectContextKicktippCommand
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Operations/CollectContext/CollectContextKicktippCommand.cs
Line coverage
85%
Covered lines: 106
Uncovered lines: 18
Coverable lines: 124
Total lines: 261
Line coverage: 85.4%
Branch coverage
81%
Covered branches: 39
Total branches: 48
Branch coverage: 81.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteAsync()87.5%8894.12%
ExecuteKicktippContextCollection()90%303093.24%
IsHistoryDocument(...)100%44100%
PrintOutcomeCollectionSummary(...)16.67%21625%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Operations/CollectContext/CollectContextKicktippCommand.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2using Spectre.Console.Cli;
 3using Spectre.Console;
 4using EHonda.KicktippAi.Core;
 5using Orchestrator.Infrastructure.Factories;
 6using Orchestrator.Services;
 7
 8namespace Orchestrator.Commands.Operations.CollectContext;
 9
 10/// <summary>
 11/// Command for collecting Kicktipp context documents and storing them in the database.
 12/// </summary>
 13public class CollectContextKicktippCommand : AsyncCommand<CollectContextKicktippSettings>
 14{
 15    private readonly IAnsiConsole _console;
 16    private readonly IFirebaseServiceFactory _firebaseServiceFactory;
 17    private readonly IKicktippClientFactory _kicktippClientFactory;
 18    private readonly IContextProviderFactory _contextProviderFactory;
 19    private readonly MatchOutcomeCollectionService _matchOutcomeCollectionService;
 20    private readonly ILogger<CollectContextKicktippCommand> _logger;
 21
 122    public CollectContextKicktippCommand(
 123        IAnsiConsole console,
 124        IFirebaseServiceFactory firebaseServiceFactory,
 125        IKicktippClientFactory kicktippClientFactory,
 126        IContextProviderFactory contextProviderFactory,
 127        MatchOutcomeCollectionService matchOutcomeCollectionService,
 128        ILogger<CollectContextKicktippCommand> logger)
 29    {
 130        _console = console;
 131        _firebaseServiceFactory = firebaseServiceFactory;
 132        _kicktippClientFactory = kicktippClientFactory;
 133        _contextProviderFactory = contextProviderFactory;
 134        _matchOutcomeCollectionService = matchOutcomeCollectionService;
 135        _logger = logger;
 136    }
 37
 38    public override async Task<int> ExecuteAsync(CommandContext context, CollectContextKicktippSettings settings)
 39    {
 40
 41        try
 42        {
 43            // Validate settings
 144            if (string.IsNullOrWhiteSpace(settings.CommunityContext))
 45            {
 146                _console.MarkupLine("[red]Error: Community context is required[/]");
 147                return 1;
 48            }
 49
 150            _console.MarkupLine($"[green]Collect-context kicktipp command initialized[/]");
 51
 152            if (settings.Verbose)
 53            {
 154                _console.MarkupLine("[dim]Verbose mode enabled[/]");
 55            }
 56
 157            if (settings.DryRun)
 58            {
 159                _console.MarkupLine("[magenta]Dry run mode enabled - no changes will be made to database[/]");
 60            }
 61
 162            if (settings.MatchOutcomesOnly)
 63            {
 064                _console.MarkupLine("[blue]Match outcomes only mode enabled - context documents will not be updated[/]")
 65            }
 66
 67            // Execute the context collection workflow
 168            await ExecuteKicktippContextCollection(settings);
 69
 170            return 0;
 71        }
 172        catch (Exception ex)
 73        {
 174            _logger.LogError(ex, "Error executing collect-context kicktipp command");
 175            _console.MarkupLine($"[red]Error:[/] {ex.Message}");
 176            return 1;
 77        }
 178    }
 79
 80    private async Task ExecuteKicktippContextCollection(CollectContextKicktippSettings settings)
 81    {
 182        var outcomeCollectionResult = await _matchOutcomeCollectionService.CollectAsync(
 183            settings.CommunityContext,
 184            settings.DryRun);
 85
 186        PrintOutcomeCollectionSummary(outcomeCollectionResult, settings);
 87
 188        if (settings.MatchOutcomesOnly)
 89        {
 090            var completionMessage = settings.DryRun
 091                ? "[magenta]✓ Match outcome dry run completed[/]"
 092                : "[green]✓ Match outcome collection completed![/]";
 093            _console.MarkupLine(completionMessage);
 094            return;
 95        }
 96
 97        // Create services using factories (factories handle env var loading)
 198        var kicktippClient = _kicktippClientFactory.CreateClient();
 199        var contextRepository = _firebaseServiceFactory.CreateContextRepository();
 100
 101        // Create context provider using factory
 1102        var contextProvider = _contextProviderFactory.CreateKicktippContextProvider(
 1103            kicktippClient, settings.CommunityContext, settings.CommunityContext);
 104
 1105        _console.MarkupLine($"[blue]Using community context:[/] [yellow]{settings.CommunityContext}[/]");
 1106        _console.MarkupLine("[blue]Getting current matchday matches...[/]");
 107
 108        // Step 1: Get current matchday matches
 1109        var matchesWithHistory = await kicktippClient.GetMatchesWithHistoryAsync(settings.CommunityContext);
 110
 1111        if (!matchesWithHistory.Any())
 112        {
 1113            _console.MarkupLine("[yellow]No matches found for current matchday[/]");
 1114            return;
 115        }
 116
 1117        _console.MarkupLine($"[green]Found {matchesWithHistory.Count} matches for current matchday[/]");
 118
 119        // Step 2: Collect all unique context documents for all matches
 1120        var allContextDocuments = new Dictionary<string, string>(); // documentName -> content
 121
 1122        foreach (var matchWithHistory in matchesWithHistory)
 123        {
 1124            var match = matchWithHistory.Match;
 1125            _console.MarkupLine($"[cyan]Collecting context for:[/] {match.HomeTeam} vs {match.AwayTeam}");
 126
 127            try
 128            {
 129                // Get context for this specific match
 1130                await foreach (var contextDoc in contextProvider.GetMatchContextAsync(match.HomeTeam, match.AwayTeam))
 131                {
 132                    // Use the document name as key to avoid duplicates
 1133                    if (!allContextDocuments.ContainsKey(contextDoc.Name))
 134                    {
 1135                        allContextDocuments[contextDoc.Name] = contextDoc.Content;
 136
 1137                        if (settings.Verbose)
 138                        {
 1139                            _console.MarkupLine($"[dim]  Collected context document: {contextDoc.Name}[/]");
 140                        }
 141                    }
 142                }
 1143            }
 1144            catch (Exception ex)
 145            {
 1146                _logger.LogError(ex, "Failed to collect context for match {HomeTeam} vs {AwayTeam}", match.HomeTeam, mat
 1147                _console.MarkupLine($"[red]  ✗ Failed to collect context: {ex.Message}[/]");
 1148            }
 1149        }
 150
 1151        _console.MarkupLine($"[green]Collected {allContextDocuments.Count} unique context documents[/]");
 152
 153        // Step 3: Save context documents to database
 1154        var savedCount = 0;
 1155        var skippedCount = 0;
 1156        var currentDate = DateTime.Now.ToString("yyyy-MM-dd");
 157
 1158        foreach (var (documentName, content) in allContextDocuments)
 159        {
 160            try
 161            {
 1162                if (settings.DryRun)
 163                {
 1164                    _console.MarkupLine($"[magenta]  Dry run - would save:[/] {documentName}");
 1165                    continue;
 166                }
 167
 168                // Check if this is a history document that needs Data_Collected_At column
 1169                string finalContent = content;
 1170                if (IsHistoryDocument(documentName))
 171                {
 172                    // Get the previous version to compare against
 1173                    var previousDocument = await contextRepository.GetLatestContextDocumentAsync(documentName, settings.
 1174                    var previousContent = previousDocument?.Content;
 175
 176                    // Add Data_Collected_At column with current date for new matches
 1177                    finalContent = HistoryCsvUtility.AddDataCollectedAtColumn(content, previousContent, currentDate);
 178
 1179                    if (settings.Verbose)
 180                    {
 1181                        _console.MarkupLine($"[dim]  Added Data_Collected_At column to {documentName}[/]");
 182                    }
 183                }
 184
 1185                var savedVersion = await contextRepository.SaveContextDocumentAsync(
 1186                    documentName,
 1187                    finalContent,
 1188                    settings.CommunityContext);
 189
 1190                if (savedVersion.HasValue)
 191                {
 1192                    savedCount++;
 1193                    if (settings.Verbose)
 194                    {
 1195                        _console.MarkupLine($"[green]  ✓ Saved {documentName} as version {savedVersion.Value}[/]");
 196                    }
 197                }
 198                else
 199                {
 1200                    skippedCount++;
 1201                    if (settings.Verbose)
 202                    {
 1203                        _console.MarkupLine($"[dim]  - Skipped {documentName} (content unchanged)[/]");
 204                    }
 205                }
 1206            }
 1207            catch (Exception ex)
 208            {
 1209                _logger.LogError(ex, "Failed to save context document {DocumentName}", documentName);
 1210                _console.MarkupLine($"[red]  ✗ Failed to save {documentName}: {ex.Message}[/]");
 1211            }
 1212        }
 213
 1214        if (settings.DryRun)
 215        {
 1216            _console.MarkupLine($"[magenta]✓ Dry run completed - would have processed {allContextDocuments.Count} docume
 217        }
 218        else
 219        {
 1220            _console.MarkupLine($"[green]✓ Context collection completed![/]");
 1221            _console.MarkupLine($"[green]  Saved: {savedCount} documents[/]");
 1222            _console.MarkupLine($"[dim]  Skipped: {skippedCount} documents (unchanged)[/]");
 223        }
 1224    }
 225
 226    private static bool IsHistoryDocument(string documentName)
 227    {
 1228        return documentName.StartsWith("recent-history-", StringComparison.OrdinalIgnoreCase) ||
 1229               documentName.StartsWith("home-history-", StringComparison.OrdinalIgnoreCase) ||
 1230               documentName.StartsWith("away-history-", StringComparison.OrdinalIgnoreCase);
 231    }
 232
 233    private void PrintOutcomeCollectionSummary(MatchOutcomeCollectionResult result, CollectContextKicktippSettings setti
 234    {
 1235        _console.MarkupLine($"[blue]Current tippuebersicht matchday:[/] [yellow]{result.CurrentMatchday}[/]");
 236
 1237        if (!result.IncompleteMatchdays.Any())
 238        {
 1239            _console.MarkupLine("[green]All persisted matchdays up to the current matchday are already complete[/]");
 1240            return;
 241        }
 242
 0243        _console.MarkupLine($"[blue]Incomplete matchdays to check:[/] [yellow]{string.Join(", ", result.IncompleteMatchd
 244
 0245        foreach (var summary in result.MatchdaySummaries)
 246        {
 0247            if (settings.DryRun)
 248            {
 0249                _console.MarkupLine(
 0250                    $"[magenta]  Dry run - would evaluate matchday {summary.Matchday}[/] " +
 0251                    $"({summary.FetchedMatches} matches, {summary.CompletedMatches} completed, {summary.PendingMatches} 
 0252                continue;
 253            }
 254
 0255            _console.MarkupLine(
 0256                $"[green]  Matchday {summary.Matchday}:[/] {summary.FetchedMatches} matches, " +
 0257                $"created {summary.CreatedCount}, updated {summary.UpdatedCount}, unchanged {summary.UnchangedCount}, " 
 0258                $"pending {summary.PendingMatches}");
 259        }
 0260    }
 261}