< Summary

Information
Class: Orchestrator.Commands.Observability.Experiments.PreparedExperimentDatasetItem
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/Experiments/PreparedExperimentBundleBuilder.cs
Line coverage
100%
Covered lines: 5
Uncovered lines: 0
Coverable lines: 5
Total lines: 239
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/Experiments/PreparedExperimentBundleBuilder.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Nodes;
 3using System.Text.Json.Serialization;
 4
 5namespace Orchestrator.Commands.Observability.Experiments;
 6
 7internal static class PreparedExperimentBundleBuilder
 8{
 9    private static readonly JsonSerializerOptions OutputJsonOptions = new(JsonSerializerDefaults.Web)
 10    {
 11        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 12        WriteIndented = true,
 13        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
 14    };
 15
 16    private static readonly JsonElement InputSchema = ParseJsonElement(
 17        """
 18        {
 19          "type": "object",
 20          "properties": {
 21            "fixture": {
 22              "type": "string",
 23              "minLength": 1,
 24              "description": "Home team vs away team in football display order"
 25            },
 26            "startsAt": {
 27              "type": "string",
 28              "minLength": 1,
 29              "description": "Localized match start timestamp string emitted by the .NET exporter"
 30            }
 31          },
 32          "required": ["fixture", "startsAt"],
 33          "additionalProperties": false
 34        }
 35        """);
 36
 37    private static readonly JsonElement ExpectedOutputSchema = ParseJsonElement(
 38        """
 39        {
 40          "type": "object",
 41          "properties": {
 42            "score": {
 43              "type": "string",
 44              "minLength": 3,
 45              "description": "Completed match score in home:away order"
 46            }
 47          },
 48          "required": ["score"],
 49          "additionalProperties": false
 50        }
 51        """);
 52
 53    public static PreparedExperimentBundle Build(
 54        IReadOnlyList<PreparedExperimentSourceItem> sourceItems,
 55        string communityContext,
 56        string sourceDatasetName,
 57        string sliceDatasetName,
 58        string sliceKey,
 59        string sliceKind,
 60        string sampleMethod,
 61        string sourcePoolKey,
 62        int? sampleSeed,
 63        string? datasetDescription = null,
 64        IReadOnlyDictionary<string, object?>? extraDatasetMetadata = null,
 65        int? matchCount = null,
 66        int? repetitions = null)
 67    {
 68        if (sourceItems.Count == 0)
 69        {
 70            throw new InvalidOperationException("At least one slice source item is required.");
 71        }
 72
 73        var first = sourceItems[0];
 74        var selectedItemIds = sourceItems
 75            .Select(item => item.SelectedItemId)
 76            .Distinct(StringComparer.Ordinal)
 77            .ToList();
 78        var selectedItemIdsHash = ExperimentArtifactSupport.ComputeSelectedItemIdsHash(selectedItemIds);
 79
 80        var artifactItems = sourceItems.Select(item => new PreparedExperimentDatasetItem(
 81                item.SliceDatasetItemId,
 82                JsonSerializer.SerializeToElement(new
 83                {
 84                    fixture = $"{item.HomeTeam} vs {item.AwayTeam}",
 85                    item.StartsAt
 86                }, OutputJsonOptions),
 87                JsonSerializer.SerializeToElement(new
 88                {
 89                    score = $"{item.ExpectedHomeGoals}:{item.ExpectedAwayGoals}"
 90                }, OutputJsonOptions),
 91                JsonSerializer.SerializeToElement(new
 92                {
 93                    item.Competition,
 94                    item.Season,
 95                    item.CommunityContext,
 96                    item.Matchday,
 97                    item.MatchdayLabel,
 98                    item.HomeTeam,
 99                    item.AwayTeam,
 100                    item.TippSpielId,
 101                    item.FixtureIndex,
 102                    item.RepetitionIndex
 103                }, OutputJsonOptions)))
 104            .ToList();
 105
 106        var manifestItems = sourceItems.Select(item => new PreparedExperimentManifestItem
 107        {
 108            SourceDatasetItemId = item.SourceDatasetItemId,
 109            SliceDatasetItemId = item.SliceDatasetItemId,
 110            HomeTeam = item.HomeTeam,
 111            AwayTeam = item.AwayTeam,
 112            Matchday = item.Matchday,
 113            StartsAt = item.StartsAt,
 114            TippSpielId = item.TippSpielId,
 115            FixtureIndex = item.FixtureIndex,
 116            RepetitionIndex = item.RepetitionIndex
 117        }).ToList();
 118
 119        var datasetMetadataNode = JsonSerializer.SerializeToNode(new
 120        {
 121            first.Competition,
 122            communityContext,
 123            scope = string.Equals(sliceKind, "single-match", StringComparison.OrdinalIgnoreCase)
 124                || string.Equals(sliceKind, "repeated-match", StringComparison.OrdinalIgnoreCase)
 125                ? "repeated-match"
 126                : string.Equals(sliceKind, "repeated-match-slice", StringComparison.OrdinalIgnoreCase)
 127                    || string.Equals(sampleMethod, "repeated-match-slice", StringComparison.OrdinalIgnoreCase)
 128                    ? "repeated-match-slice"
 129                : string.Equals(sliceKind, "community-to-date", StringComparison.OrdinalIgnoreCase)
 130                    || string.Equals(sampleMethod, "community-to-date", StringComparison.OrdinalIgnoreCase)
 131                    ? "community-to-date"
 132                : "match-slice",
 133            first.Season,
 134            sliceKey,
 135            sliceKind,
 136            sampleMethod,
 137            sampleSeed,
 138            sampleSize = sourceItems.Count,
 139            matchCount,
 140            repetitions,
 141            sourceDatasetName,
 142            sourcePoolKey
 143        }, OutputJsonOptions) as JsonObject ?? new JsonObject();
 144        AddDatasetMetadata(datasetMetadataNode, extraDatasetMetadata);
 145        var datasetMetadata = JsonSerializer.SerializeToElement(datasetMetadataNode, OutputJsonOptions);
 146
 147        var artifact = new PreparedExperimentDataset(
 148            sliceDatasetName,
 149            string.IsNullOrWhiteSpace(datasetDescription)
 150                ? $"{sliceKind} dataset for {sourceItems.Count} item(s) on {sliceKey}"
 151                : datasetDescription.Trim(),
 152            datasetMetadata,
 153            InputSchema,
 154            ExpectedOutputSchema,
 155            artifactItems);
 156
 157        var manifest = new PreparedExperimentManifest
 158        {
 159            SliceKey = sliceKey,
 160            SliceKind = sliceKind,
 161            SampleMethod = sampleMethod,
 162            CommunityContext = communityContext,
 163            SourcePoolKey = sourcePoolKey,
 164            SourceDatasetName = sourceDatasetName,
 165            SliceDatasetName = sliceDatasetName,
 166            Competition = first.Competition,
 167            Season = first.Season,
 168            SampleSeed = sampleSeed,
 169            SampleSize = sourceItems.Count,
 170            MatchCount = matchCount,
 171            Repetitions = repetitions,
 172            SelectedItemIds = selectedItemIds,
 173            SelectedItemIdsHash = selectedItemIdsHash,
 174            Items = manifestItems
 175        };
 176
 177        return new PreparedExperimentBundle(artifact, manifest);
 178    }
 179
 180    private static void AddDatasetMetadata(JsonObject datasetMetadata, IReadOnlyDictionary<string, object?>? extraDatase
 181    {
 182        if (extraDatasetMetadata is null)
 183        {
 184            return;
 185        }
 186
 187        foreach (var (key, value) in extraDatasetMetadata)
 188        {
 189            if (string.IsNullOrWhiteSpace(key) || value is null)
 190            {
 191                continue;
 192            }
 193
 194            datasetMetadata[key] = JsonSerializer.SerializeToNode(value, OutputJsonOptions);
 195        }
 196    }
 197
 198    private static JsonElement ParseJsonElement(string value)
 199    {
 200        using var document = JsonDocument.Parse(value);
 201        return document.RootElement.Clone();
 202    }
 203}
 204
 205internal sealed record PreparedExperimentSourceItem(
 206    string SourceDatasetItemId,
 207    string SliceDatasetItemId,
 208    string SelectedItemId,
 209    string Competition,
 210    string Season,
 211    string CommunityContext,
 212    int Matchday,
 213    string MatchdayLabel,
 214    string HomeTeam,
 215    string AwayTeam,
 216    string StartsAt,
 217    string TippSpielId,
 218    int ExpectedHomeGoals,
 219    int ExpectedAwayGoals,
 220    int? FixtureIndex = null,
 221    int? RepetitionIndex = null);
 222
 223internal sealed record PreparedExperimentBundle(
 224    PreparedExperimentDataset Artifact,
 225    PreparedExperimentManifest Manifest);
 226
 227internal sealed record PreparedExperimentDataset(
 228    [property: JsonPropertyName("datasetName")] string DatasetName,
 229    [property: JsonPropertyName("datasetDescription")] string DatasetDescription,
 230    [property: JsonPropertyName("datasetMetadata")] JsonElement DatasetMetadata,
 231    [property: JsonPropertyName("inputSchema")] JsonElement InputSchema,
 232    [property: JsonPropertyName("expectedOutputSchema")] JsonElement ExpectedOutputSchema,
 233    [property: JsonPropertyName("items")] IReadOnlyList<PreparedExperimentDatasetItem> Items);
 234
 1235internal sealed record PreparedExperimentDatasetItem(
 1236    [property: JsonPropertyName("id")] string Id,
 1237    [property: JsonPropertyName("input")] JsonElement Input,
 1238    [property: JsonPropertyName("expectedOutput")] JsonElement ExpectedOutput,
 1239    [property: JsonPropertyName("metadata")] JsonElement Metadata);