< Summary

Information
Class: Orchestrator.Commands.Observability.Experiments.PreparedExperimentBundleBuilder
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/Experiments/PreparedExperimentBundleBuilder.cs
Line coverage
99%
Covered lines: 150
Uncovered lines: 1
Coverable lines: 151
Total lines: 239
Line coverage: 99.3%
Branch coverage
93%
Covered branches: 30
Total branches: 32
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Build(...)91.67%242498.7%
AddDatasetMetadata(...)100%88100%
ParseJsonElement(...)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{
 19    private static readonly JsonSerializerOptions OutputJsonOptions = new(JsonSerializerDefaults.Web)
 110    {
 111        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 112        WriteIndented = true,
 113        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
 114    };
 15
 116    private static readonly JsonElement InputSchema = ParseJsonElement(
 117        """
 118        {
 119          "type": "object",
 120          "properties": {
 121            "fixture": {
 122              "type": "string",
 123              "minLength": 1,
 124              "description": "Home team vs away team in football display order"
 125            },
 126            "startsAt": {
 127              "type": "string",
 128              "minLength": 1,
 129              "description": "Localized match start timestamp string emitted by the .NET exporter"
 130            }
 131          },
 132          "required": ["fixture", "startsAt"],
 133          "additionalProperties": false
 134        }
 135        """);
 36
 137    private static readonly JsonElement ExpectedOutputSchema = ParseJsonElement(
 138        """
 139        {
 140          "type": "object",
 141          "properties": {
 142            "score": {
 143              "type": "string",
 144              "minLength": 3,
 145              "description": "Completed match score in home:away order"
 146            }
 147          },
 148          "required": ["score"],
 149          "additionalProperties": false
 150        }
 151        """);
 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    {
 168        if (sourceItems.Count == 0)
 69        {
 070            throw new InvalidOperationException("At least one slice source item is required.");
 71        }
 72
 173        var first = sourceItems[0];
 174        var selectedItemIds = sourceItems
 175            .Select(item => item.SelectedItemId)
 176            .Distinct(StringComparer.Ordinal)
 177            .ToList();
 178        var selectedItemIdsHash = ExperimentArtifactSupport.ComputeSelectedItemIdsHash(selectedItemIds);
 79
 180        var artifactItems = sourceItems.Select(item => new PreparedExperimentDatasetItem(
 181                item.SliceDatasetItemId,
 182                JsonSerializer.SerializeToElement(new
 183                {
 184                    fixture = $"{item.HomeTeam} vs {item.AwayTeam}",
 185                    item.StartsAt
 186                }, OutputJsonOptions),
 187                JsonSerializer.SerializeToElement(new
 188                {
 189                    score = $"{item.ExpectedHomeGoals}:{item.ExpectedAwayGoals}"
 190                }, OutputJsonOptions),
 191                JsonSerializer.SerializeToElement(new
 192                {
 193                    item.Competition,
 194                    item.Season,
 195                    item.CommunityContext,
 196                    item.Matchday,
 197                    item.MatchdayLabel,
 198                    item.HomeTeam,
 199                    item.AwayTeam,
 1100                    item.TippSpielId,
 1101                    item.FixtureIndex,
 1102                    item.RepetitionIndex
 1103                }, OutputJsonOptions)))
 1104            .ToList();
 105
 1106        var manifestItems = sourceItems.Select(item => new PreparedExperimentManifestItem
 1107        {
 1108            SourceDatasetItemId = item.SourceDatasetItemId,
 1109            SliceDatasetItemId = item.SliceDatasetItemId,
 1110            HomeTeam = item.HomeTeam,
 1111            AwayTeam = item.AwayTeam,
 1112            Matchday = item.Matchday,
 1113            StartsAt = item.StartsAt,
 1114            TippSpielId = item.TippSpielId,
 1115            FixtureIndex = item.FixtureIndex,
 1116            RepetitionIndex = item.RepetitionIndex
 1117        }).ToList();
 118
 1119        var datasetMetadataNode = JsonSerializer.SerializeToNode(new
 1120        {
 1121            first.Competition,
 1122            communityContext,
 1123            scope = string.Equals(sliceKind, "single-match", StringComparison.OrdinalIgnoreCase)
 1124                || string.Equals(sliceKind, "repeated-match", StringComparison.OrdinalIgnoreCase)
 1125                ? "repeated-match"
 1126                : string.Equals(sliceKind, "repeated-match-slice", StringComparison.OrdinalIgnoreCase)
 1127                    || string.Equals(sampleMethod, "repeated-match-slice", StringComparison.OrdinalIgnoreCase)
 1128                    ? "repeated-match-slice"
 1129                : string.Equals(sliceKind, "community-to-date", StringComparison.OrdinalIgnoreCase)
 1130                    || string.Equals(sampleMethod, "community-to-date", StringComparison.OrdinalIgnoreCase)
 1131                    ? "community-to-date"
 1132                : "match-slice",
 1133            first.Season,
 1134            sliceKey,
 1135            sliceKind,
 1136            sampleMethod,
 1137            sampleSeed,
 1138            sampleSize = sourceItems.Count,
 1139            matchCount,
 1140            repetitions,
 1141            sourceDatasetName,
 1142            sourcePoolKey
 1143        }, OutputJsonOptions) as JsonObject ?? new JsonObject();
 1144        AddDatasetMetadata(datasetMetadataNode, extraDatasetMetadata);
 1145        var datasetMetadata = JsonSerializer.SerializeToElement(datasetMetadataNode, OutputJsonOptions);
 146
 1147        var artifact = new PreparedExperimentDataset(
 1148            sliceDatasetName,
 1149            string.IsNullOrWhiteSpace(datasetDescription)
 1150                ? $"{sliceKind} dataset for {sourceItems.Count} item(s) on {sliceKey}"
 1151                : datasetDescription.Trim(),
 1152            datasetMetadata,
 1153            InputSchema,
 1154            ExpectedOutputSchema,
 1155            artifactItems);
 156
 1157        var manifest = new PreparedExperimentManifest
 1158        {
 1159            SliceKey = sliceKey,
 1160            SliceKind = sliceKind,
 1161            SampleMethod = sampleMethod,
 1162            CommunityContext = communityContext,
 1163            SourcePoolKey = sourcePoolKey,
 1164            SourceDatasetName = sourceDatasetName,
 1165            SliceDatasetName = sliceDatasetName,
 1166            Competition = first.Competition,
 1167            Season = first.Season,
 1168            SampleSeed = sampleSeed,
 1169            SampleSize = sourceItems.Count,
 1170            MatchCount = matchCount,
 1171            Repetitions = repetitions,
 1172            SelectedItemIds = selectedItemIds,
 1173            SelectedItemIdsHash = selectedItemIdsHash,
 1174            Items = manifestItems
 1175        };
 176
 1177        return new PreparedExperimentBundle(artifact, manifest);
 178    }
 179
 180    private static void AddDatasetMetadata(JsonObject datasetMetadata, IReadOnlyDictionary<string, object?>? extraDatase
 181    {
 1182        if (extraDatasetMetadata is null)
 183        {
 1184            return;
 185        }
 186
 1187        foreach (var (key, value) in extraDatasetMetadata)
 188        {
 1189            if (string.IsNullOrWhiteSpace(key) || value is null)
 190            {
 191                continue;
 192            }
 193
 1194            datasetMetadata[key] = JsonSerializer.SerializeToNode(value, OutputJsonOptions);
 195        }
 1196    }
 197
 198    private static JsonElement ParseJsonElement(string value)
 199    {
 1200        using var document = JsonDocument.Parse(value);
 1201        return document.RootElement.Clone();
 1202    }
 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
 235internal sealed record PreparedExperimentDatasetItem(
 236    [property: JsonPropertyName("id")] string Id,
 237    [property: JsonPropertyName("input")] JsonElement Input,
 238    [property: JsonPropertyName("expectedOutput")] JsonElement ExpectedOutput,
 239    [property: JsonPropertyName("metadata")] JsonElement Metadata);