96 lines
2.4 KiB
Go
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
|
|
}
|