fix: deliver validated AI insight responses

This commit is contained in:
2026-08-22 15:47:38 +08:00
parent ca55c2fb3d
commit 3fba4261ee
68 changed files with 6870 additions and 493 deletions

View File

@@ -9,6 +9,8 @@ import (
"os/signal"
"time"
"git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/aiprovider"
"git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/coach"
"git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/config"
"git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/database"
"git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/growthreport"
@@ -41,29 +43,55 @@ func main() {
coordinator := growthreport.NewCoordinator(redisClient, growthreport.CoordinatorConfig{CreatePerMinute: cfg.AICreatePerMinute, UncachedPerDay: cfg.AIUncachedScopesPerDay, DailyRequests: cfg.AIDailyRequestBudget, DailyTokens: cfg.AIDailyTokenBudget, MaxConcurrent: cfg.AIMaxConcurrent})
avalai := growthreport.NewAvalAIClient(growthreport.AvalAIConfig{BaseURL: cfg.AvalAIBaseURL, APIKey: cfg.AvalAIAPIKey, Model: cfg.AvalAIModel, Timeout: 30 * time.Second}, &http.Client{})
processor := growthreport.NewProcessor(repository, growthreport.NewPostgreSQLBuilder(pool), avalai, coordinator, cfg.AIGrowthEnabled && cfg.AvalAIAPIKey != "")
insightService := insight.NewService(insight.NewRepository(pool), insight.NewPostgreSQLBuilder(pool))
sharedProvider := aiprovider.New(aiprovider.Config{BaseURL: cfg.AvalAIBaseURL, APIKey: cfg.AvalAIAPIKey, Model: cfg.AvalAIModel, Timeout: 30 * time.Second}, &http.Client{})
var insightService *insight.Service
if cfg.AIPageInsightsEnabled && cfg.AvalAIAPIKey != "" {
insightService = insight.NewService(insight.NewRepository(pool), insight.NewPostgreSQLBuilder(pool), insight.NewAvalAIGenerator(sharedProvider)).WithBudget(cfg.AIPageInsightsDailyRequestBudget, cfg.AIPageInsightsDailyTokenBudget)
} else {
insightService = insight.NewService(insight.NewRepository(pool), insight.NewPostgreSQLBuilder(pool))
}
var coachGenerator coach.Generator
if cfg.AICoachEnabled && cfg.AvalAIAPIKey != "" {
coachGenerator = coach.NewAvalAIGenerator(sharedProvider)
}
coachService := coach.NewService(coach.NewRepository(pool), coachGenerator).WithBudget(cfg.AICoachDailyRequestBudget, cfg.AICoachDailyTokenBudget)
logger.Info("growth worker ready", "ai_enabled", cfg.AIGrowthEnabled && cfg.AvalAIAPIKey != "")
poll := time.NewTicker(time.Second)
defer poll.Stop()
cleanup := time.NewTicker(24 * time.Hour)
defer cleanup.Stop()
go runInsightScheduler(ctx, logger, insightService)
evaluation := time.NewTicker(time.Minute)
defer evaluation.Stop()
if cfg.AIPageInsightsEnabled {
go runInsightScheduler(ctx, logger, insightService)
}
for {
select {
case <-ctx.Done():
return
case <-poll.C:
var err error
if cfg.AIPageInsightsEnabled {
insightCtx, insightCancel := context.WithTimeout(ctx, 2*time.Minute)
err = insightService.ProcessNext(insightCtx)
insightCancel()
if err != nil && !errors.Is(err, insight.ErrNotFound) {
logger.Error("page insight processing failed", "error", err)
}
}
workCtx, workCancel := context.WithTimeout(ctx, 2*time.Minute)
err := processor.ProcessNext(workCtx)
err = processor.ProcessNext(workCtx)
workCancel()
if err != nil && !errors.Is(err, growthreport.ErrNotFound) {
logger.Error("growth report processing failed", "error", err)
}
insightCtx, insightCancel := context.WithTimeout(ctx, 2*time.Minute)
err = insightService.ProcessNext(insightCtx)
insightCancel()
if err != nil && !errors.Is(err, insight.ErrNotFound) {
logger.Error("page insight processing failed", "error", err)
if cfg.AICoachEnabled {
coachCtx, coachCancel := context.WithTimeout(ctx, 2*time.Minute)
err = coachService.ProcessNext(coachCtx)
coachCancel()
if err != nil && !errors.Is(err, coach.ErrNotFound) {
logger.Error("growth coach processing failed", "error", err)
}
}
case <-cleanup.C:
cleanupCtx, cleanupCancel := context.WithTimeout(ctx, 30*time.Second)
@@ -74,6 +102,25 @@ func main() {
} else if deleted > 0 {
logger.Info("growth cleanup complete", "deleted_reports", deleted)
}
case <-evaluation.C:
if cfg.AICoachEnabled {
evaluationCtx, evaluationCancel := context.WithTimeout(ctx, time.Minute)
completed, err := coachService.EvaluateMetrics(evaluationCtx)
evaluationCancel()
if err != nil {
logger.Error("coach metric evaluation failed", "error", err)
} else if completed > 0 {
logger.Info("coach metric goals completed", "count", completed)
}
difficultyCtx, difficultyCancel := context.WithTimeout(ctx, time.Minute)
proposals, err := coachService.EvaluateDifficulty(difficultyCtx)
difficultyCancel()
if err != nil {
logger.Error("coach difficulty evaluation failed", "error", err)
} else if proposals > 0 {
logger.Info("coach difficulty proposals created", "count", proposals)
}
}
}
}
}