< Summary

Information
Class: EHonda.KicktippAi.Core.MatchPrediction
Assembly: EHonda.KicktippAi.Core
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Core/IPredictionRepository.cs
Line coverage
100%
Covered lines: 3
Uncovered lines: 0
Coverable lines: 3
Total lines: 389
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/Core/IPredictionRepository.cs

#LineLine coverage
 1using NodaTime;
 2
 3namespace EHonda.KicktippAi.Core;
 4
 5/// <summary>
 6/// Repository interface for persisting and retrieving match predictions.
 7/// Currently designed for Bundesliga 2025/26 season.
 8/// </summary>
 9public interface IPredictionRepository
 10{
 11    /// <summary>
 12    /// Saves a prediction for a specific match.
 13    /// </summary>
 14    /// <param name="match">The match to predict.</param>
 15    /// <param name="prediction">The prediction for the match.</param>
 16    /// <param name="model">The AI model used to generate the prediction.</param>
 17    /// <param name="tokenUsage">JSON string containing token usage information from the API.</param>
 18    /// <param name="cost">The cost in USD for generating the prediction.</param>
 19    /// <param name="communityContext">The community context (rules) used for the prediction.</param>
 20    /// <param name="contextDocumentNames">Names of context documents used for this prediction.</param>
 21    /// <param name="cancellationToken">Cancellation token.</param>
 22    /// <returns>A task representing the asynchronous operation.</returns>
 23    Task SavePredictionAsync(Match match, Prediction prediction, string model, string tokenUsage, double cost, string co
 24    Task SavePredictionAsync(Match match, Prediction prediction, PredictionModelConfig modelConfig, string tokenUsage, d
 25
 26    /// <summary>
 27    /// Retrieves a prediction for a specific match using the specified model and community context.
 28    /// </summary>
 29    /// <param name="match">The match to get the prediction for.</param>
 30    /// <param name="model">The AI model used to generate the prediction.</param>
 31    /// <param name="communityContext">The community context used for the prediction.</param>
 32    /// <param name="cancellationToken">Cancellation token.</param>
 33    /// <returns>The prediction if found, otherwise null.</returns>
 34    Task<Prediction?> GetPredictionAsync(Match match, string model, string communityContext, CancellationToken cancellat
 35    Task<Prediction?> GetPredictionAsync(Match match, PredictionModelConfig modelConfig, string communityContext, Cancel
 36
 37    /// <summary>
 38    /// Retrieves full prediction metadata for a specific match using the specified model and community context.
 39    /// Includes context document names and timestamps for outdated checks.
 40    /// </summary>
 41    /// <param name="match">The match to get the prediction for.</param>
 42    /// <param name="model">The AI model used to generate the prediction.</param>
 43    /// <param name="communityContext">The community context used for the prediction.</param>
 44    /// <param name="cancellationToken">Cancellation token.</param>
 45    /// <returns>The prediction metadata if found, otherwise null.</returns>
 46    Task<PredictionMetadata?> GetPredictionMetadataAsync(Match match, string model, string communityContext, Cancellatio
 47    Task<PredictionMetadata?> GetPredictionMetadataAsync(Match match, PredictionModelConfig modelConfig, string communit
 48
 49    /// <summary>
 50    /// Retrieves a prediction for a specific match by team names, start time, model, and community context.
 51    /// </summary>
 52    /// <param name="homeTeam">The home team name.</param>
 53    /// <param name="awayTeam">The away team name.</param>
 54    /// <param name="startsAt">The match start time.</param>
 55    /// <param name="model">The AI model used to generate the prediction.</param>
 56    /// <param name="communityContext">The community context used for the prediction.</param>
 57    /// <param name="cancellationToken">Cancellation token.</param>
 58    /// <returns>The prediction if found, otherwise null.</returns>
 59    Task<Prediction?> GetPredictionAsync(string homeTeam, string awayTeam, ZonedDateTime startsAt, string model, string 
 60    Task<Prediction?> GetPredictionAsync(string homeTeam, string awayTeam, ZonedDateTime startsAt, PredictionModelConfig
 61
 62    /// <summary>
 63    /// Retrieves the latest predicted match for a team pairing using database-side filtering.
 64    /// </summary>
 65    /// <param name="homeTeam">The home team name.</param>
 66    /// <param name="awayTeam">The away team name.</param>
 67    /// <param name="communityContext">The community context used for the prediction.</param>
 68    /// <param name="cancellationToken">Cancellation token.</param>
 69    /// <returns>The latest predicted match by start time if found, otherwise null.</returns>
 70    Task<Match?> GetLatestPredictedMatchByTeamsAsync(string homeTeam, string awayTeam, string communityContext, Cancella
 71
 72    /// <summary>
 73    /// Retrieves all matches for a specific match day.
 74    /// </summary>
 75    /// <param name="matchDay">The match day number (1-34 for Bundesliga).</param>
 76    /// <param name="cancellationToken">Cancellation token.</param>
 77    /// <returns>A collection of matches for the specified match day.</returns>
 78    Task<IReadOnlyList<Match>> GetMatchDayAsync(int matchDay, CancellationToken cancellationToken = default);
 79
 80    /// <summary>
 81    /// Retrieves a stored match by team names and matchday.
 82    /// </summary>
 83    /// <param name="homeTeam">The home team name.</param>
 84    /// <param name="awayTeam">The away team name.</param>
 85    /// <param name="matchDay">The match day number.</param>
 86    /// <param name="model">Optional model used to narrow prediction-based fallback lookups.</param>
 87    /// <param name="communityContext">Optional community context used to narrow prediction-based fallback lookups.</par
 88    /// <param name="cancellationToken">Cancellation token.</param>
 89    /// <returns>The stored match if found, otherwise null.</returns>
 90    Task<Match?> GetStoredMatchAsync(string homeTeam, string awayTeam, int matchDay, string? model = null, string? commu
 91    Task<Match?> GetStoredMatchAsync(string homeTeam, string awayTeam, int matchDay, PredictionModelConfig? modelConfig,
 92
 93    /// <summary>
 94    /// Retrieves all matches with their predictions for a specific match day using the specified model and community co
 95    /// </summary>
 96    /// <param name="matchDay">The match day number (1-34 for Bundesliga).</param>
 97    /// <param name="model">The AI model used to generate the predictions.</param>
 98    /// <param name="communityContext">The community context used for the predictions.</param>
 99    /// <param name="cancellationToken">Cancellation token.</param>
 100    /// <returns>A collection of matches with their predictions (if any) for the specified match day.</returns>
 101    Task<IReadOnlyList<MatchPrediction>> GetMatchDayWithPredictionsAsync(int matchDay, string model, string communityCon
 102    Task<IReadOnlyList<MatchPrediction>> GetMatchDayWithPredictionsAsync(int matchDay, PredictionModelConfig modelConfig
 103
 104    /// <summary>
 105    /// Retrieves all predictions made with the specified model and community context.
 106    /// </summary>
 107    /// <param name="model">The AI model used to generate the predictions.</param>
 108    /// <param name="communityContext">The community context used for the predictions.</param>
 109    /// <param name="cancellationToken">Cancellation token.</param>
 110    /// <returns>A collection of all match predictions for the specified model and community context.</returns>
 111    Task<IReadOnlyList<MatchPrediction>> GetAllPredictionsAsync(string model, string communityContext, CancellationToken
 112    Task<IReadOnlyList<MatchPrediction>> GetAllPredictionsAsync(PredictionModelConfig modelConfig, string communityConte
 113
 114    /// <summary>
 115    /// Checks if a prediction exists for a specific match using the specified model and community context.
 116    /// </summary>
 117    /// <param name="match">The match to check.</param>
 118    /// <param name="model">The AI model used to generate the prediction.</param>
 119    /// <param name="communityContext">The community context used for the prediction.</param>
 120    /// <param name="cancellationToken">Cancellation token.</param>
 121    /// <returns>True if a prediction exists, otherwise false.</returns>
 122    Task<bool> HasPredictionAsync(Match match, string model, string communityContext, CancellationToken cancellationToke
 123    Task<bool> HasPredictionAsync(Match match, PredictionModelConfig modelConfig, string communityContext, CancellationT
 124
 125    /// <summary>
 126    /// Saves a bonus prediction for a specific question.
 127    /// </summary>
 128    /// <param name="bonusQuestion">The original bonus question (for text observability).</param>
 129    /// <param name="bonusPrediction">The bonus prediction to save.</param>
 130    /// <param name="model">The AI model used to generate the prediction.</param>
 131    /// <param name="tokenUsage">JSON string containing token usage information from the API.</param>
 132    /// <param name="cost">The cost in USD for generating the prediction.</param>
 133    /// <param name="communityContext">The community context (rules) used for the prediction.</param>
 134    /// <param name="contextDocumentNames">Names of context documents used for this prediction.</param>
 135    /// <param name="cancellationToken">Cancellation token.</param>
 136    /// <returns>A task representing the asynchronous operation.</returns>
 137    Task SaveBonusPredictionAsync(BonusQuestion bonusQuestion, BonusPrediction bonusPrediction, string model, string tok
 138    Task SaveBonusPredictionAsync(BonusQuestion bonusQuestion, BonusPrediction bonusPrediction, PredictionModelConfig mo
 139
 140    /// <summary>
 141    /// Retrieves a bonus prediction for a specific question using the specified model and community context.
 142    /// </summary>
 143    /// <param name="questionId">The ID of the bonus question.</param>
 144    /// <param name="model">The AI model used to generate the prediction.</param>
 145    /// <param name="communityContext">The community context used for the prediction.</param>
 146    /// <param name="cancellationToken">Cancellation token.</param>
 147    /// <returns>The bonus prediction if found, otherwise null.</returns>
 148    Task<BonusPrediction?> GetBonusPredictionAsync(string questionId, string model, string communityContext, Cancellatio
 149    Task<BonusPrediction?> GetBonusPredictionAsync(string questionId, PredictionModelConfig modelConfig, string communit
 150
 151    /// <summary>
 152    /// Retrieves a bonus prediction by question text and community context.
 153    /// This allows lookup for the same question text with different form IDs.
 154    /// </summary>
 155    /// <param name="questionText">The text of the bonus question.</param>
 156    /// <param name="model">The AI model used to generate the prediction.</param>
 157    /// <param name="communityContext">The community context used for the prediction.</param>
 158    /// <param name="cancellationToken">Cancellation token.</param>
 159    /// <returns>The bonus prediction if found, otherwise null.</returns>
 160    Task<BonusPrediction?> GetBonusPredictionByTextAsync(string questionText, string model, string communityContext, Can
 161    Task<BonusPrediction?> GetBonusPredictionByTextAsync(string questionText, PredictionModelConfig modelConfig, string 
 162
 163    /// <summary>
 164    /// Retrieves full bonus prediction metadata by question text and community context.
 165    /// Includes context document names and timestamps for outdated checks.
 166    /// </summary>
 167    /// <param name="questionText">The text of the bonus question.</param>
 168    /// <param name="model">The AI model used to generate the prediction.</param>
 169    /// <param name="communityContext">The community context used for the prediction.</param>
 170    /// <param name="cancellationToken">Cancellation token.</param>
 171    /// <returns>The bonus prediction metadata if found, otherwise null.</returns>
 172    Task<BonusPredictionMetadata?> GetBonusPredictionMetadataByTextAsync(string questionText, string model, string commu
 173    Task<BonusPredictionMetadata?> GetBonusPredictionMetadataByTextAsync(string questionText, PredictionModelConfig mode
 174
 175    /// <summary>
 176    /// Retrieves all bonus predictions made with the specified model and community context.
 177    /// </summary>
 178    /// <param name="model">The AI model used to generate the predictions.</param>
 179    /// <param name="communityContext">The community context used for the predictions.</param>
 180    /// <param name="cancellationToken">Cancellation token.</param>
 181    /// <returns>A collection of all bonus predictions for the specified model and community context.</returns>
 182    Task<IReadOnlyList<BonusPrediction>> GetAllBonusPredictionsAsync(string model, string communityContext, Cancellation
 183    Task<IReadOnlyList<BonusPrediction>> GetAllBonusPredictionsAsync(PredictionModelConfig modelConfig, string community
 184
 185    /// <summary>
 186    /// Checks if a bonus prediction exists for a specific question using the specified model and community context.
 187    /// </summary>
 188    /// <param name="questionId">The ID of the bonus question.</param>
 189    /// <param name="model">The AI model used to generate the prediction.</param>
 190    /// <param name="communityContext">The community context used for the prediction.</param>
 191    /// <param name="cancellationToken">Cancellation token.</param>
 192    /// <returns>True if a bonus prediction exists, otherwise false.</returns>
 193    Task<bool> HasBonusPredictionAsync(string questionId, string model, string communityContext, CancellationToken cance
 194    Task<bool> HasBonusPredictionAsync(string questionId, PredictionModelConfig modelConfig, string communityContext, Ca
 195
 196    /// <summary>
 197    /// Gets the current reprediction index for a specific match using the specified model and community context.
 198    /// </summary>
 199    /// <param name="match">The match to check.</param>
 200    /// <param name="model">The AI model used to generate the prediction.</param>
 201    /// <param name="communityContext">The community context used for the prediction.</param>
 202    /// <param name="cancellationToken">Cancellation token.</param>
 203    /// <returns>The current reprediction index, or -1 if no prediction exists.</returns>
 204    Task<int> GetMatchRepredictionIndexAsync(Match match, string model, string communityContext, CancellationToken cance
 205    Task<int> GetMatchRepredictionIndexAsync(Match match, PredictionModelConfig modelConfig, string communityContext, Ca
 206
 207    // ============================================================================
 208    // CANCELLED MATCH LOOKUPS (Team Names Only - No startsAt Constraint)
 209    // ============================================================================
 210    //
 211    // These methods exist to handle a specific edge case with cancelled matches:
 212    //
 213    // PROBLEM:
 214    // When a match is cancelled ("Abgesagt" on Kicktipp), the match time is no longer
 215    // displayed. Different Kicktipp pages handle this inconsistently:
 216    //   - tippabgabe page: Shows multiple matches in a table, allowing time inheritance
 217    //     from the previous row (e.g., 15:30 from the match above)
 218    //   - spielinfo pages: Show one match at a time, so there's no "previous row" to
 219    //     inherit from, resulting in DateTimeOffset.MinValue as fallback
 220    //
 221    // This causes the same cancelled match to have different `startsAt` values depending
 222    // on which page was scraped, leading to database lookup mismatches:
 223    //   - MatchdayCommand uses spielinfo â†’ startsAt = MinValue
 224    //   - VerifyCommand uses tippabgabe â†’ startsAt = inherited time (e.g., 15:30)
 225    //
 226    // SOLUTION:
 227    // For cancelled matches ONLY, we query by team names without the startsAt constraint.
 228    // This finds the prediction regardless of which startsAt value was used when storing.
 229    // We order by createdAt descending to get the most recent prediction.
 230    //
 231    // WHY NOT CHANGE THE NORMAL FLOW:
 232    // The startsAt constraint is important for non-cancelled matches because:
 233    //   1. It's part of the natural composite key (teams can play multiple times)
 234    //   2. It ensures we don't accidentally retrieve predictions for rescheduled matches
 235    //   3. It maintains data integrity for the vast majority of matches
 236    //
 237    // Cancelled matches are rare edge cases where this constraint causes more problems
 238    // than it solves, so we relax it only for this specific scenario.
 239    // ============================================================================
 240
 241    /// <summary>
 242    /// Retrieves a prediction for a cancelled match by team names only (ignoring startsAt).
 243    /// <para>
 244    /// This method should ONLY be used for cancelled matches where the startsAt value
 245    /// may be inconsistent across different Kicktipp pages. For normal matches, use
 246    /// <see cref="GetPredictionAsync(Match, string, string, CancellationToken)"/> instead.
 247    /// </para>
 248    /// </summary>
 249    /// <param name="homeTeam">The home team name.</param>
 250    /// <param name="awayTeam">The away team name.</param>
 251    /// <param name="model">The AI model used to generate the prediction.</param>
 252    /// <param name="communityContext">The community context used for the prediction.</param>
 253    /// <param name="cancellationToken">Cancellation token.</param>
 254    /// <returns>The most recent prediction if found, otherwise null.</returns>
 255    Task<Prediction?> GetCancelledMatchPredictionAsync(string homeTeam, string awayTeam, string model, string communityC
 256    Task<Prediction?> GetCancelledMatchPredictionAsync(string homeTeam, string awayTeam, PredictionModelConfig modelConf
 257
 258    /// <summary>
 259    /// Retrieves prediction metadata for a cancelled match by team names only (ignoring startsAt).
 260    /// <para>
 261    /// This method should ONLY be used for cancelled matches where the startsAt value
 262    /// may be inconsistent across different Kicktipp pages. For normal matches, use
 263    /// <see cref="GetPredictionMetadataAsync(Match, string, string, CancellationToken)"/> instead.
 264    /// </para>
 265    /// </summary>
 266    /// <param name="homeTeam">The home team name.</param>
 267    /// <param name="awayTeam">The away team name.</param>
 268    /// <param name="model">The AI model used to generate the prediction.</param>
 269    /// <param name="communityContext">The community context used for the prediction.</param>
 270    /// <param name="cancellationToken">Cancellation token.</param>
 271    /// <returns>The most recent prediction metadata if found, otherwise null.</returns>
 272    Task<PredictionMetadata?> GetCancelledMatchPredictionMetadataAsync(string homeTeam, string awayTeam, string model, s
 273    Task<PredictionMetadata?> GetCancelledMatchPredictionMetadataAsync(string homeTeam, string awayTeam, PredictionModel
 274
 275    /// <summary>
 276    /// Gets the reprediction index for a cancelled match by team names only (ignoring startsAt).
 277    /// <para>
 278    /// This method should ONLY be used for cancelled matches where the startsAt value
 279    /// may be inconsistent across different Kicktipp pages. For normal matches, use
 280    /// <see cref="GetMatchRepredictionIndexAsync(Match, string, string, CancellationToken)"/> instead.
 281    /// </para>
 282    /// </summary>
 283    /// <param name="homeTeam">The home team name.</param>
 284    /// <param name="awayTeam">The away team name.</param>
 285    /// <param name="model">The AI model used to generate the prediction.</param>
 286    /// <param name="communityContext">The community context used for the prediction.</param>
 287    /// <param name="cancellationToken">Cancellation token.</param>
 288    /// <returns>The current reprediction index, or -1 if no prediction exists.</returns>
 289    Task<int> GetCancelledMatchRepredictionIndexAsync(string homeTeam, string awayTeam, string model, string communityCo
 290    Task<int> GetCancelledMatchRepredictionIndexAsync(string homeTeam, string awayTeam, PredictionModelConfig modelConfi
 291
 292    /// <summary>
 293    /// Gets the current reprediction index for a specific bonus question using the specified model and community contex
 294    /// </summary>
 295    /// <param name="questionText">The text of the bonus question.</param>
 296    /// <param name="model">The AI model used to generate the prediction.</param>
 297    /// <param name="communityContext">The community context used for the prediction.</param>
 298    /// <param name="cancellationToken">Cancellation token.</param>
 299    /// <returns>The current reprediction index, or -1 if no prediction exists.</returns>
 300    Task<int> GetBonusRepredictionIndexAsync(string questionText, string model, string communityContext, CancellationTok
 301    Task<int> GetBonusRepredictionIndexAsync(string questionText, PredictionModelConfig modelConfig, string communityCon
 302
 303    /// <summary>
 304    /// Saves a repredicted match prediction with the next reprediction index.
 305    /// </summary>
 306    /// <param name="match">The match to predict.</param>
 307    /// <param name="prediction">The prediction for the match.</param>
 308    /// <param name="model">The AI model used to generate the prediction.</param>
 309    /// <param name="tokenUsage">JSON string containing token usage information from the API.</param>
 310    /// <param name="cost">The cost in USD for generating the prediction.</param>
 311    /// <param name="communityContext">The community context (rules) used for the prediction.</param>
 312    /// <param name="contextDocumentNames">Names of context documents used for this prediction.</param>
 313    /// <param name="repredictionIndex">The reprediction index for this prediction.</param>
 314    /// <param name="cancellationToken">Cancellation token.</param>
 315    /// <returns>A task representing the asynchronous operation.</returns>
 316    Task SaveRepredictionAsync(Match match, Prediction prediction, string model, string tokenUsage, double cost, string 
 317    Task SaveRepredictionAsync(Match match, Prediction prediction, PredictionModelConfig modelConfig, string tokenUsage,
 318
 319    /// <summary>
 320    /// Saves a repredicted bonus prediction with the next reprediction index.
 321    /// </summary>
 322    /// <param name="bonusQuestion">The original bonus question (for text observability).</param>
 323    /// <param name="bonusPrediction">The bonus prediction to save.</param>
 324    /// <param name="model">The AI model used to generate the prediction.</param>
 325    /// <param name="tokenUsage">JSON string containing token usage information from the API.</param>
 326    /// <param name="cost">The cost in USD for generating the prediction.</param>
 327    /// <param name="communityContext">The community context (rules) used for the prediction.</param>
 328    /// <param name="contextDocumentNames">Names of context documents used for this prediction.</param>
 329    /// <param name="repredictionIndex">The reprediction index for this prediction.</param>
 330    /// <param name="cancellationToken">Cancellation token.</param>
 331    /// <returns>A task representing the asynchronous operation.</returns>
 332    Task SaveBonusRepredictionAsync(BonusQuestion bonusQuestion, BonusPrediction bonusPrediction, string model, string t
 333    Task SaveBonusRepredictionAsync(BonusQuestion bonusQuestion, BonusPrediction bonusPrediction, PredictionModelConfig 
 334
 335    /// <summary>
 336    /// Get match prediction costs and counts grouped by reprediction index for cost analysis.
 337    /// Used specifically by the cost command to include all repredictions.
 338    /// </summary>
 339    /// <param name="model">The AI model used to generate predictions.</param>
 340    /// <param name="communityContext">The community context used for predictions.</param>
 341    /// <param name="matchdays">Optional list of matchdays to filter by. If null, all matchdays are included.</param>
 342    /// <param name="cancellationToken">Cancellation token.</param>
 343    /// <returns>Dictionary mapping reprediction index to (cost, count) tuple.</returns>
 344    Task<Dictionary<int, (double cost, int count)>> GetMatchPredictionCostsByRepredictionIndexAsync(string model, string
 345    Task<Dictionary<int, (double cost, int count)>> GetMatchPredictionCostsByRepredictionIndexAsync(PredictionModelConfi
 346
 347    /// <summary>
 348    /// Get bonus prediction costs and counts grouped by reprediction index for cost analysis.
 349    /// Used specifically by the cost command to include all repredictions.
 350    /// </summary>
 351    /// <param name="model">The AI model used to generate predictions.</param>
 352    /// <param name="communityContext">The community context used for predictions.</param>
 353    /// <param name="cancellationToken">Cancellation token.</param>
 354    /// <returns>Dictionary mapping reprediction index to (cost, count) tuple.</returns>
 355    Task<Dictionary<int, (double cost, int count)>> GetBonusPredictionCostsByRepredictionIndexAsync(string model, string
 356    Task<Dictionary<int, (double cost, int count)>> GetBonusPredictionCostsByRepredictionIndexAsync(PredictionModelConfi
 357
 358    /// <summary>
 359    /// Gets all unique matchdays that have predictions stored.
 360    /// Used by the cost command to discover available matchdays when no filter is specified.
 361    /// </summary>
 362    /// <param name="cancellationToken">Cancellation token.</param>
 363    /// <returns>A sorted list of matchday numbers.</returns>
 364    Task<List<int>> GetAvailableMatchdaysAsync(CancellationToken cancellationToken = default);
 365
 366    /// <summary>
 367    /// Gets all unique AI models that have predictions stored.
 368    /// Used by the cost command to discover available models when no filter is specified.
 369    /// </summary>
 370    /// <param name="cancellationToken">Cancellation token.</param>
 371    /// <returns>A list of model names.</returns>
 372    Task<List<string>> GetAvailableModelsAsync(CancellationToken cancellationToken = default);
 373    Task<List<PredictionModelConfig>> GetAvailableModelConfigsAsync(CancellationToken cancellationToken = default);
 374
 375    /// <summary>
 376    /// Gets all unique community contexts that have predictions stored.
 377    /// Used by the cost command to discover available community contexts when no filter is specified.
 378    /// </summary>
 379    /// <param name="cancellationToken">Cancellation token.</param>
 380    /// <returns>A list of community context names.</returns>
 381    Task<List<string>> GetAvailableCommunityContextsAsync(CancellationToken cancellationToken = default);
 382}
 383
 384/// <summary>
 385/// Represents a match combined with its prediction (if any).
 386/// </summary>
 1387public record MatchPrediction(
 1388    Match Match,
 1389    Prediction? Prediction);