< Summary

Information
Class: OpenAiIntegration.OpenAiPredictor
Assembly: OpenAiIntegration
File(s): /home/runner/work/KicktippAi/KicktippAi/src/OpenAiIntegration/OpenAiPredictor.cs
Line coverage
91%
Covered lines: 56
Uncovered lines: 5
Coverable lines: 61
Total lines: 124
Line coverage: 91.8%
Branch coverage
83%
Covered branches: 20
Total branches: 24
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)83.33%66100%
PredictAsync()100%11100%
GeneratePrompt(...)100%11100%
ParsePrediction(...)83.33%211878.26%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/OpenAiIntegration/OpenAiPredictor.cs

#LineLine coverage
 1using System.ClientModel;
 2using EHonda.KicktippAi.Core;
 3using Microsoft.Extensions.Logging;
 4using OpenAI.Responses;
 5
 6namespace OpenAiIntegration;
 7
 8public class OpenAiPredictor : IPredictor<PredictorContext>
 9{
 10    private readonly ResponsesClient _client;
 11    private readonly ILogger<OpenAiPredictor> _logger;
 12    private readonly string _model;
 13
 114    public OpenAiPredictor(ResponsesClient client, ILogger<OpenAiPredictor> logger, string model = "gpt-4o-mini")
 15    {
 116        _client = client ?? throw new ArgumentNullException(nameof(client));
 117        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 118        _model = model ?? throw new ArgumentNullException(nameof(model));
 119    }
 20
 21    public async Task<Prediction> PredictAsync(Match match, PredictorContext context, CancellationToken cancellationToke
 22    {
 123        _logger.LogInformation("Generating prediction for match: {HomeTeam} vs {AwayTeam} at {StartTime}",
 124            match.HomeTeam, match.AwayTeam, match.StartsAt);
 25
 26        try
 27        {
 128            var prompt = GeneratePrompt(match, context);
 129            _logger.LogDebug("Generated prompt: {Prompt}", prompt);
 30
 131            var response = await _client.CreateResponseAsync(_model, prompt, cancellationToken: cancellationToken);
 132            _logger.LogDebug("Received response from OpenAI");
 33
 134            var prediction = ParsePrediction(response);
 135            _logger.LogInformation("Prediction generated: {HomeGoals}-{AwayGoals} for {HomeTeam} vs {AwayTeam}",
 136                prediction.HomeGoals, prediction.AwayGoals, match.HomeTeam, match.AwayTeam);
 37
 138            return prediction;
 39        }
 140        catch (Exception ex)
 41        {
 142            _logger.LogError(ex, "Error generating prediction for match: {HomeTeam} vs {AwayTeam}",
 143                match.HomeTeam, match.AwayTeam);
 44
 45            // Return a fallback prediction in case of error
 146            _logger.LogWarning("Returning fallback prediction (1-1) due to error");
 147            return new Prediction(1, 1);
 48        }
 149    }
 50
 51    private string GeneratePrompt(Match match, PredictorContext context)
 52    {
 153        var prompt = $@"You are a football prediction expert. Predict the final score for this match:
 154
 155Match: {match.HomeTeam} vs {match.AwayTeam}
 156Kick-off: {match.StartsAt:yyyy-MM-dd HH:mm}
 157
 158Please provide your prediction in the following format only:
 159HOME_GOALS-AWAY_GOALS
 160
 161For example: 2-1
 162
 163Consider:
 164- Home advantage (home teams typically score slightly more)
 165- Recent form and performance
 166- Common football scores (0-0, 1-0, 1-1, 2-0, 2-1, etc.)
 167
 168Your prediction:";
 69
 170        return prompt;
 71    }
 72
 73    private Prediction ParsePrediction(ClientResult<ResponseResult>? response)
 74    {
 75        try
 76        {
 177            if (response?.Value is null)
 78            {
 079                _logger.LogWarning("No content in OpenAI response, using fallback prediction");
 080                return new Prediction(1, 1);
 81            }
 82
 183            var content = response.Value.GetOutputText()?.Trim();
 184            if (string.IsNullOrEmpty(content))
 85            {
 186                _logger.LogWarning("Empty content in OpenAI response, using fallback prediction");
 187                return new Prediction(1, 1);
 88            }
 89
 190            _logger.LogDebug("Parsing response content: {Content}", content);
 91
 92            // Look for pattern like "2-1" in the response
 193            var scorePattern = System.Text.RegularExpressions.Regex.Match(content, @"(\d+)-(\d+)");
 94
 195            if (scorePattern.Success)
 96            {
 197                var homeGoals = int.Parse(scorePattern.Groups[1].Value);
 198                var awayGoals = int.Parse(scorePattern.Groups[2].Value);
 99
 100                // Validate reasonable score range (0-10 goals per team)
 1101                if (homeGoals >= 0 && homeGoals <= 10 && awayGoals >= 0 && awayGoals <= 10)
 102                {
 1103                    return new Prediction(homeGoals, awayGoals);
 104                }
 105                else
 106                {
 1107                    _logger.LogWarning("Parsed scores out of reasonable range: {HomeGoals}-{AwayGoals}, using fallback",
 1108                        homeGoals, awayGoals);
 1109                    return new Prediction(1, 1);
 110                }
 111            }
 112            else
 113            {
 1114                _logger.LogWarning("Could not parse score from response: {Content}, using fallback prediction", content)
 1115                return new Prediction(1, 1);
 116            }
 117        }
 0118        catch (Exception ex)
 119        {
 0120            _logger.LogError(ex, "Error parsing prediction response, using fallback prediction");
 0121            return new Prediction(1, 1);
 122        }
 1123    }
 124}