< Summary

Information
Class: Orchestrator.Commands.Observability.AnalyzeMatch.AnalyzeMatchCommandHelpers
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/AnalyzeMatch/AnalyzeMatchCommandHelpers.cs
Line coverage
100%
Covered lines: 110
Uncovered lines: 0
Coverable lines: 110
Total lines: 201
Line coverage: 100%
Branch coverage
95%
Covered branches: 42
Total branches: 44
Branch coverage: 95.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CreateLoggerFactory(...)100%11100%
ResolveMatchAsync()83.33%66100%
GetMatchContextDocumentsAsync()100%2020100%
GetTeamAbbreviation(...)90%1010100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/AnalyzeMatch/AnalyzeMatchCommandHelpers.cs

#LineLine coverage
 1using System.Text;
 2using EHonda.KicktippAi.Core;
 3using Microsoft.Extensions.Logging;
 4using Spectre.Console;
 5using FirebaseAdapter;
 6using KicktippIntegration;
 7
 8namespace Orchestrator.Commands.Observability.AnalyzeMatch;
 9
 10internal sealed record AnalyzeMatchContextDocumentInfo(DocumentContext Document, int Version);
 11
 12internal static class AnalyzeMatchCommandHelpers
 13{
 14    public static ILoggerFactory CreateLoggerFactory(bool debug)
 15    {
 116        return LoggerFactory.Create(builder =>
 117        {
 118            builder.AddSimpleConsole(options =>
 119            {
 120                options.SingleLine = true;
 121                options.IncludeScopes = false;
 122                options.ColorBehavior = Microsoft.Extensions.Logging.Console.LoggerColorBehavior.Enabled;
 123            });
 124            builder.SetMinimumLevel(debug ? LogLevel.Information : LogLevel.Error);
 125        });
 26    }
 27
 28    public static async Task<Match?> ResolveMatchAsync(
 29        AnalyzeMatchBaseSettings settings,
 30        IKicktippClient? kicktippClient,
 31        ILogger logger,
 32        string communityContext,
 33        IAnsiConsole console)
 34    {
 35
 136        if (kicktippClient != null)
 37        {
 38            try
 39            {
 140                var matches = await kicktippClient.GetMatchesWithHistoryAsync(communityContext);
 141                var found = matches.FirstOrDefault(m =>
 142                    m.Match.Matchday == settings.Matchday &&
 143                    string.Equals(m.Match.HomeTeam, settings.HomeTeam, StringComparison.OrdinalIgnoreCase) &&
 144                    string.Equals(m.Match.AwayTeam, settings.AwayTeam, StringComparison.OrdinalIgnoreCase));
 45
 146                if (found != null)
 47                {
 148                    console.MarkupLine("[dim]Using match metadata from Kicktipp schedule[/]");
 149                    return found.Match;
 50                }
 51
 152                logger.LogWarning(
 153                    "Match not found via Kicktipp lookup for community {CommunityContext}, matchday {Matchday}, teams {H
 154                    communityContext,
 155                    settings.Matchday,
 156                    settings.HomeTeam,
 157                    settings.AwayTeam);
 158            }
 159            catch (Exception ex)
 60            {
 161                logger.LogWarning(ex, "Failed to fetch match metadata from Kicktipp; continuing with provided details");
 162            }
 63        }
 64        else
 65        {
 166            logger.LogWarning("Kicktipp client not configured; continuing with provided match details");
 67        }
 68
 169        return new Match(
 170            settings.HomeTeam,
 171            settings.AwayTeam,
 172            default,
 173            settings.Matchday!.Value);
 174    }
 75
 76    public static async Task<List<AnalyzeMatchContextDocumentInfo>> GetMatchContextDocumentsAsync(
 77        IContextRepository contextRepository,
 78        string homeTeam,
 79        string awayTeam,
 80        string communityContext,
 81        bool verbose,
 82        IAnsiConsole console)
 83    {
 184        var contextDocuments = new List<AnalyzeMatchContextDocumentInfo>();
 185        var homeAbbreviation = GetTeamAbbreviation(homeTeam);
 186        var awayAbbreviation = GetTeamAbbreviation(awayTeam);
 87
 188        var requiredDocuments = new[]
 189        {
 190            "bundesliga-standings.csv",
 191            $"community-rules-{communityContext}.md",
 192            $"recent-history-{homeAbbreviation}.csv",
 193            $"recent-history-{awayAbbreviation}.csv",
 194            $"home-history-{homeAbbreviation}.csv",
 195            $"away-history-{awayAbbreviation}.csv",
 196            $"head-to-head-{homeAbbreviation}-vs-{awayAbbreviation}.csv"
 197        };
 98
 199        var optionalDocuments = new[]
 1100        {
 1101            $"{homeAbbreviation}-transfers.csv",
 1102            $"{awayAbbreviation}-transfers.csv"
 1103        };
 104
 1105        if (verbose)
 106        {
 1107            console.MarkupLine($"[dim]Looking for {requiredDocuments.Length} required context documents in database[/]")
 108        }
 109
 1110        foreach (var documentName in requiredDocuments)
 111        {
 1112            var contextDoc = await contextRepository.GetLatestContextDocumentAsync(documentName, communityContext);
 1113            if (contextDoc != null)
 114            {
 1115                contextDocuments.Add(new AnalyzeMatchContextDocumentInfo(new DocumentContext(contextDoc.DocumentName, co
 116
 1117                if (verbose)
 118                {
 1119                    console.MarkupLine($"[dim]  ✓ Retrieved {documentName} (version {contextDoc.Version})[/]");
 120                }
 121            }
 1122            else if (verbose)
 123            {
 1124                console.MarkupLine($"[dim]  ✗ Missing {documentName}[/]");
 125            }
 1126        }
 127
 1128        foreach (var documentName in optionalDocuments)
 129        {
 130            try
 131            {
 1132                var contextDoc = await contextRepository.GetLatestContextDocumentAsync(documentName, communityContext);
 1133                if (contextDoc != null)
 134                {
 1135                    contextDocuments.Add(new AnalyzeMatchContextDocumentInfo(new DocumentContext(contextDoc.DocumentName
 136
 1137                    if (verbose)
 138                    {
 1139                        console.MarkupLine($"[dim]  ✓ Retrieved optional {documentName} (version {contextDoc.Version})[/
 140                    }
 141                }
 1142                else if (verbose)
 143                {
 1144                    console.MarkupLine($"[dim]  · Missing optional {documentName}[/]");
 145                }
 1146            }
 1147            catch (Exception ex)
 148            {
 1149                if (verbose)
 150                {
 1151                    console.MarkupLine($"[dim]  · Failed optional {documentName}: {Markup.Escape(ex.Message)}[/]");
 152                }
 1153            }
 1154        }
 155
 1156        return contextDocuments;
 1157    }
 158
 159    private static string GetTeamAbbreviation(string teamName)
 160    {
 1161        var abbreviations = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
 1162        {
 1163            { "1. FC Heidenheim 1846", "fch" },
 1164            { "1. FC Köln", "fck" },
 1165            { "1. FC Union Berlin", "fcu" },
 1166            { "1899 Hoffenheim", "tsg" },
 1167            { "Bayer 04 Leverkusen", "b04" },
 1168            { "Bor. Mönchengladbach", "bmg" },
 1169            { "Borussia Dortmund", "bvb" },
 1170            { "Eintracht Frankfurt", "sge" },
 1171            { "FC Augsburg", "fca" },
 1172            { "FC Bayern München", "fcb" },
 1173            { "FC St. Pauli", "fcs" },
 1174            { "FSV Mainz 05", "m05" },
 1175            { "Hamburger SV", "hsv" },
 1176            { "RB Leipzig", "rbl" },
 1177            { "SC Freiburg", "scf" },
 1178            { "VfB Stuttgart", "vfb" },
 1179            { "VfL Wolfsburg", "wob" },
 1180            { "Werder Bremen", "svw" }
 1181        };
 182
 1183        if (abbreviations.TryGetValue(teamName, out var abbreviation))
 184        {
 1185            return abbreviation;
 186        }
 187
 1188        var words = teamName.Split(' ', StringSplitOptions.RemoveEmptyEntries);
 1189        var builder = new StringBuilder();
 190
 1191        foreach (var word in words.Take(3))
 192        {
 1193            if (word.Length > 0 && char.IsLetter(word[0]))
 194            {
 1195                builder.Append(char.ToLowerInvariant(word[0]));
 196            }
 197        }
 198
 1199        return builder.Length > 0 ? builder.ToString() : "unknown";
 200    }
 201}