Files
shooting-event/backend/ai_handlers.go
2026-04-01 11:47:03 +04:00

96 lines
2.4 KiB
Go

package main
import (
"database/sql"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
)
func (a *App) handleScoreAdvice(c echo.Context) error {
stage, err := validateStage(c.Param("stage"))
if err != nil {
return writeError(c, http.StatusBadRequest, err.Error())
}
playerID, err := strconv.Atoi(c.Param("id"))
if err != nil {
return writeError(c, http.StatusBadRequest, "invalid player id")
}
if strings.TrimSpace(a.cfg.GeminiAPIKey) == "" {
return writeError(c, http.StatusServiceUnavailable, "gemini api key is not configured")
}
proofImageData, err := a.loadScoreProofImage(stage, playerID)
if err != nil {
if err == sql.ErrNoRows {
return writeError(c, http.StatusBadRequest, "no proof image found for this score")
}
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("load proof image: %v", err))
}
if strings.TrimSpace(proofImageData) == "" {
return writeError(c, http.StatusBadRequest, "no proof image found for this score")
}
currentScore, _ := a.loadCurrentScore(stage, playerID)
advice, err := a.generateScoreAdvice(stage, playerID, proofImageData, currentScore)
if err != nil {
return writeError(c, http.StatusBadGateway, err.Error())
}
return c.JSON(http.StatusOK, advice)
}
func (a *App) loadScoreProofImage(stage string, playerID int) (string, error) {
var imageData string
err := a.db.QueryRow(`SELECT image_data FROM score_attachments WHERE stage = ? AND player_id = ?`, stage, playerID).Scan(&imageData)
if err != nil {
return "", err
}
return imageData, nil
}
func (a *App) loadCurrentScore(stage string, playerID int) (int, error) {
var score int
err := a.db.QueryRow(`SELECT score FROM scores WHERE stage = ? AND player_id = ?`, stage, playerID).Scan(&score)
if err != nil {
if err == sql.ErrNoRows {
return 0, nil
}
return 0, err
}
return score, nil
}
func (a *App) buildAdviceResponse(stage string, playerID int, raw scoreAdviceModelResponse) ScoreAdviceResponse {
advised := clampInt(raw.AdvisedScore, 0, 9999)
reason := strings.TrimSpace(raw.Reason)
if reason == "" {
reason = "AI estimated the score from visible impacts."
}
return ScoreAdviceResponse{
Stage: stage,
PlayerID: playerID,
AdvisedScore: advised,
Reason: reason,
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
}
}
func clampInt(value, min, max int) int {
if value < min {
return min
}
if value > max {
return max
}
return value
}