< Summary

Information
Class: EHonda.KicktippAi.Core.HistoryCsvUtility
Assembly: EHonda.KicktippAi.Core
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Core/HistoryCsvUtility.cs
Line coverage
97%
Covered lines: 85
Uncovered lines: 2
Coverable lines: 87
Total lines: 206
Line coverage: 97.7%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddDataCollectedAtColumn(...)100%44100%
HasDataCollectedAtColumn(...)100%22100%
ExtractMatches(...)100%22100%
ExtractMatchesWithCollectionDates(...)100%4490.48%
BuildCsvWithDataCollectedAt(...)100%44100%
CreateMatchKey(...)100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Core/HistoryCsvUtility.cs

#LineLine coverage
 1using System.Globalization;
 2using CsvHelper;
 3
 4namespace EHonda.KicktippAi.Core;
 5
 6/// <summary>
 7/// Utility class for handling Data_Collected_At column in history CSV documents.
 8/// </summary>
 9public static class HistoryCsvUtility
 10{
 11    /// <summary>
 12    /// Adds or updates the Data_Collected_At column in a history CSV document.
 13    /// </summary>
 14    /// <param name="csvContent">The original CSV content.</param>
 15    /// <param name="previousCsvContent">The previous version of the CSV content (null if this is the first version).</p
 16    /// <param name="collectedDate">The date when the data was collected (e.g., "2025-08-30").</param>
 17    /// <returns>The updated CSV content with Data_Collected_At column.</returns>
 18    public static string AddDataCollectedAtColumn(string csvContent, string? previousCsvContent, string collectedDate)
 19    {
 20        // Check if the CSV already has Data_Collected_At column
 121        if (HasDataCollectedAtColumn(csvContent))
 22        {
 123            return csvContent; // Already has the column
 24        }
 25
 26        // Extract matches from previous version to get their collection dates
 127        var previousMatches = previousCsvContent != null
 128            ? ExtractMatchesWithCollectionDates(previousCsvContent)
 129            : new Dictionary<string, string>();
 30
 31        // Extract current matches
 132        var currentMatches = ExtractMatches(csvContent);
 33
 34        // Build the new CSV with Data_Collected_At column
 135        return BuildCsvWithDataCollectedAt(csvContent, currentMatches, previousMatches, collectedDate);
 36    }
 37
 38    /// <summary>
 39    /// Checks if the CSV content already has a Data_Collected_At column.
 40    /// </summary>
 41    private static bool HasDataCollectedAtColumn(string csvContent)
 42    {
 143        var lines = csvContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
 144        if (lines.Length == 0)
 45        {
 146            return false;
 47        }
 48
 149        var header = lines[0];
 150        return header.Contains("Data_Collected_At", StringComparison.OrdinalIgnoreCase);
 51    }
 52
 53    /// <summary>
 54    /// Extracts matches from CSV content without Data_Collected_At.
 55    /// </summary>
 56    private static HashSet<string> ExtractMatches(string csvContent)
 57    {
 158        var matches = new HashSet<string>();
 59
 160        using var reader = new StringReader(csvContent);
 161        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
 62
 63        try
 64        {
 165            csv.Read();
 166            csv.ReadHeader();
 67
 168            while (csv.Read())
 69            {
 170                var competition = csv.GetField("Competition") ?? "";
 171                var homeTeam = csv.GetField("Home_Team") ?? "";
 172                var awayTeam = csv.GetField("Away_Team") ?? "";
 173                var score = csv.GetField("Score") ?? "";
 174                var annotation = (csv.TryGetField<string>("Annotation", out var ann) ? ann : null) ?? "";
 75
 176                var matchKey = CreateMatchKey(competition, homeTeam, awayTeam, score, annotation);
 177                matches.Add(matchKey);
 78            }
 179        }
 180        catch (Exception)
 81        {
 82            // If CSV parsing fails, return empty set
 183        }
 84
 185        return matches;
 186    }
 87
 88    /// <summary>
 89    /// Extracts matches with their collection dates from CSV content that has Data_Collected_At.
 90    /// </summary>
 91    private static Dictionary<string, string> ExtractMatchesWithCollectionDates(string csvContent)
 92    {
 193        var matches = new Dictionary<string, string>();
 94
 195        if (!HasDataCollectedAtColumn(csvContent))
 96        {
 197            return matches;
 98        }
 99
 1100        using var reader = new StringReader(csvContent);
 1101        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
 102
 103        try
 104        {
 1105            csv.Read();
 1106            csv.ReadHeader();
 107
 1108            while (csv.Read())
 109            {
 1110                var competition = csv.GetField("Competition") ?? "";
 1111                var dataCollectedAt = csv.GetField("Data_Collected_At") ?? "";
 1112                var homeTeam = csv.GetField("Home_Team") ?? "";
 1113                var awayTeam = csv.GetField("Away_Team") ?? "";
 1114                var score = csv.GetField("Score") ?? "";
 1115                var annotation = (csv.TryGetField<string>("Annotation", out var ann) ? ann : null) ?? "";
 116
 1117                var matchKey = CreateMatchKey(competition, homeTeam, awayTeam, score, annotation);
 1118                matches[matchKey] = dataCollectedAt;
 119            }
 1120        }
 0121        catch (Exception)
 122        {
 123            // If CSV parsing fails, return empty dictionary
 0124        }
 125
 1126        return matches;
 1127    }
 128
 129    /// <summary>
 130    /// Builds a new CSV with the Data_Collected_At column.
 131    /// </summary>
 132    private static string BuildCsvWithDataCollectedAt(
 133        string originalCsvContent,
 134        HashSet<string> currentMatches,
 135        Dictionary<string, string> previousMatches,
 136        string collectedDate)
 137    {
 1138        using var reader = new StringReader(originalCsvContent);
 1139        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
 140
 1141        using var writer = new StringWriter();
 1142        using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);
 143
 144        try
 145        {
 1146            csv.Read();
 1147            csv.ReadHeader();
 148
 149            // Write new header with Data_Collected_At after Competition
 1150            csvWriter.WriteField("Competition");
 1151            csvWriter.WriteField("Data_Collected_At");
 1152            csvWriter.WriteField("Home_Team");
 1153            csvWriter.WriteField("Away_Team");
 1154            csvWriter.WriteField("Score");
 1155            csvWriter.WriteField("Annotation");
 1156            csvWriter.NextRecord();
 157
 1158            while (csv.Read())
 159            {
 1160                var competition = csv.GetField("Competition") ?? "";
 1161                var homeTeam = csv.GetField("Home_Team") ?? "";
 1162                var awayTeam = csv.GetField("Away_Team") ?? "";
 1163                var score = csv.GetField("Score") ?? "";
 1164                var annotation = (csv.TryGetField<string>("Annotation", out var ann) ? ann : null) ?? "";
 165
 1166                var matchKey = CreateMatchKey(competition, homeTeam, awayTeam, score, annotation);
 167
 168                // Determine the collection date for this match
 169                string dataCollectedAt;
 1170                if (previousMatches.TryGetValue(matchKey, out var existingDate))
 171                {
 172                    // Match existed in previous version, use its existing date
 1173                    dataCollectedAt = existingDate;
 174                }
 175                else
 176                {
 177                    // New match, use current collection date
 1178                    dataCollectedAt = collectedDate;
 179                }
 180
 1181                csvWriter.WriteField(competition);
 1182                csvWriter.WriteField(dataCollectedAt);
 1183                csvWriter.WriteField(homeTeam);
 1184                csvWriter.WriteField(awayTeam);
 1185                csvWriter.WriteField(score);
 1186                csvWriter.WriteField(annotation);
 1187                csvWriter.NextRecord();
 188            }
 1189        }
 1190        catch (Exception)
 191        {
 192            // If parsing fails, return original content
 1193            return originalCsvContent;
 194        }
 195
 1196        return writer.ToString();
 1197    }
 198
 199    /// <summary>
 200    /// Creates a unique key for a match.
 201    /// </summary>
 202    private static string CreateMatchKey(string competition, string homeTeam, string awayTeam, string score, string anno
 203    {
 1204        return $"{competition}|{homeTeam}|{awayTeam}|{score}|{annotation}";
 205    }
 206}