Refactor AI pipeline handling to support daily runs
continuous-integration/drone/push Build is passing

- Renamed and refactored the AI pipeline auto run functionality to a daily run, enhancing clarity and purpose.
- Introduced a new `AIPipelineDailyWorker` to manage the daily execution of the AI pipeline, replacing the previous auto run implementation.
- Updated configuration fields and logging messages to reflect the change from auto to daily run, ensuring consistent terminology throughout the codebase.
- Removed the obsolete `ai_pipeline_auto.go` file to streamline the worker structure.

This update improves the maintainability and readability of the AI pipeline management by clearly distinguishing between auto and daily run functionalities.
This commit is contained in:
Mazyar
2026-07-07 12:17:03 +03:30
parent 89faa08b1c
commit 1ad0206e61
5 changed files with 92 additions and 88 deletions
+32 -24
View File
@@ -2,6 +2,7 @@ package bootstrap
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
@@ -19,8 +20,8 @@ import (
"tm/pkg/schedule" "tm/pkg/schedule"
) )
// aiPipelineAutoRunMu ensures only one AI pipeline auto run executes at a time (startup catch-up and cron). // aiPipelineDailyRunMu ensures only one AI pipeline daily-run executes at a time (startup catch-up and cron).
var aiPipelineAutoRunMu sync.Mutex var aiPipelineDailyRunMu sync.Mutex
// Init Application Configuration // Init Application Configuration
func InitConfig() (*Config, error) { func InitConfig() (*Config, error) {
@@ -94,8 +95,8 @@ func InitWorker(config Config, mongoManager *mongo.ConnectionManager, appLogger
"translation_enabled": config.Worker.TranslationEnabled, "translation_enabled": config.Worker.TranslationEnabled,
"translation_interval": config.Worker.TranslationInterval, "translation_interval": config.Worker.TranslationInterval,
"notification_interval": config.Worker.NotificationInterval, "notification_interval": config.Worker.NotificationInterval,
"ai_pipeline_auto_enabled": config.Worker.AIPipelineAutoEnabled, "ai_pipeline_daily_enabled": config.Worker.AIPipelineAutoEnabled,
"ai_pipeline_auto_interval": config.Worker.AIPipelineAutoInterval, "ai_pipeline_daily_interval": config.Worker.AIPipelineAutoInterval,
}) })
// Initialize repositories // Initialize repositories
noticeRepo := notice.NewRepository(mongoManager, appLogger) noticeRepo := notice.NewRepository(mongoManager, appLogger)
@@ -207,28 +208,28 @@ func InitWorker(config Config, mongoManager *mongo.ConnectionManager, appLogger
}) })
if !config.Worker.AIPipelineAutoEnabled { if !config.Worker.AIPipelineAutoEnabled {
appLogger.Info("AI pipeline auto worker disabled by configuration", map[string]interface{}{ appLogger.Info("AI pipeline daily-run worker disabled by configuration", map[string]interface{}{
"ai_pipeline_auto_enabled": false, "ai_pipeline_daily_enabled": false,
}) })
} else if aiClient != nil { } else if aiClient != nil {
dailyJobTracker := schedule.NewDailyJobTracker(mongoManager, appLogger) dailyJobTracker := schedule.NewDailyJobTracker(mongoManager, appLogger)
runAIPipelineAuto := func(trigger string) { runAIPipelineDaily := func(trigger string) {
aiPipelineAutoRunMu.Lock() aiPipelineDailyRunMu.Lock()
defer aiPipelineAutoRunMu.Unlock() defer aiPipelineDailyRunMu.Unlock()
ctx := context.Background() ctx := context.Background()
today := time.Now().Local() today := time.Now().Local()
completed, checkErr := dailyJobTracker.IsCompleted(ctx, schedule.AIPipelineAutoJobName, today) completed, checkErr := dailyJobTracker.IsCompleted(ctx, schedule.AIPipelineDailyJobName, today)
if checkErr != nil { if checkErr != nil {
appLogger.Error("Failed to check AI pipeline auto completion status", map[string]interface{}{ appLogger.Error("Failed to check AI pipeline daily-run completion status", map[string]interface{}{
"trigger": trigger, "trigger": trigger,
"error": checkErr.Error(), "error": checkErr.Error(),
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
}) })
} else if completed { } else if completed {
appLogger.Info("AI pipeline auto skipped: already completed today", map[string]interface{}{ appLogger.Info("AI pipeline daily-run skipped: already completed today", map[string]interface{}{
"trigger": trigger, "trigger": trigger,
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
}) })
@@ -236,53 +237,60 @@ func InitWorker(config Config, mongoManager *mongo.ConnectionManager, appLogger
} }
if trigger == "startup" { if trigger == "startup" {
appLogger.Info("Running startup catch-up for today's AI pipeline auto", map[string]interface{}{ appLogger.Info("Running startup catch-up for today's AI pipeline daily-run", map[string]interface{}{
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
}) })
} else { } else {
appLogger.Info("Running scheduled AI pipeline auto", map[string]interface{}{ appLogger.Info("Running scheduled AI pipeline daily-run", map[string]interface{}{
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
}) })
} }
worker := workers.NewAIPipelineAutoWorker(appLogger, aiClient) worker := workers.NewAIPipelineDailyWorker(appLogger, aiClient)
if err := worker.Run(); err != nil { if err := worker.Run(); err != nil {
appLogger.Error("AI pipeline auto run failed", map[string]interface{}{ if errors.Is(err, ai_summarizer.ErrPipelineJobRunning) {
appLogger.Info("AI pipeline daily-run deferred: job already running", map[string]interface{}{
"trigger": trigger,
"date": today.Format("02/01/2006"),
})
} else {
appLogger.Error("AI pipeline daily-run failed", map[string]interface{}{
"trigger": trigger, "trigger": trigger,
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
"error": err.Error(), "error": err.Error(),
}) })
}
return return
} }
if markErr := dailyJobTracker.MarkCompleted(ctx, schedule.AIPipelineAutoJobName, today); markErr != nil { if markErr := dailyJobTracker.MarkCompleted(ctx, schedule.AIPipelineDailyJobName, today); markErr != nil {
appLogger.Error("Failed to mark AI pipeline auto as completed", map[string]interface{}{ appLogger.Error("Failed to mark AI pipeline daily-run as completed", map[string]interface{}{
"trigger": trigger, "trigger": trigger,
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
"error": markErr.Error(), "error": markErr.Error(),
}) })
} }
appLogger.Info("AI pipeline auto run completed successfully", map[string]interface{}{ appLogger.Info("AI pipeline daily-run completed successfully", map[string]interface{}{
"trigger": trigger, "trigger": trigger,
"date": today.Format("02/01/2006"), "date": today.Format("02/01/2006"),
}) })
} }
scheduler.AddJob(schedule.Job{ scheduler.AddJob(schedule.Job{
Name: "AI Pipeline Auto Job", Name: "AI Pipeline Daily Run Job",
Func: func() { runAIPipelineAuto("scheduled") }, Func: func() { runAIPipelineDaily("scheduled") },
Expr: config.Worker.AIPipelineAutoInterval, Expr: config.Worker.AIPipelineAutoInterval,
}) })
appLogger.Info("Scheduled AI pipeline auto worker", map[string]interface{}{ appLogger.Info("Scheduled AI pipeline daily-run worker", map[string]interface{}{
"interval": config.Worker.AIPipelineAutoInterval, "interval": config.Worker.AIPipelineAutoInterval,
}) })
go func() { go func() {
runAIPipelineAuto("startup") runAIPipelineDaily("startup")
}() }()
} else { } else {
appLogger.Warn("AI summarizer client not available, AI pipeline auto worker is disabled", map[string]interface{}{}) appLogger.Warn("AI summarizer client not available, AI pipeline daily-run worker is disabled", map[string]interface{}{})
} }
// After a server restart, process any unprocessed notices (including today's) without blocking startup. // After a server restart, process any unprocessed notices (including today's) without blocking startup.
+2 -2
View File
@@ -35,10 +35,10 @@ type WorkerConfig struct {
TranslationEnabled bool `env:"WORKER_TRANSLATION_ENABLED" envDefault:"true"` TranslationEnabled bool `env:"WORKER_TRANSLATION_ENABLED" envDefault:"true"`
// NotificationInterval schedules promotion of due scheduled notifications from pending to sent. // NotificationInterval schedules promotion of due scheduled notifications from pending to sent.
NotificationInterval string `env:"WORKER_NOTIFICATION_INTERVAL" envDefault:"0 * * * * *"` NotificationInterval string `env:"WORKER_NOTIFICATION_INTERVAL" envDefault:"0 * * * * *"`
// AIPipelineAutoEnabled schedules the daily AI pipeline auto run on the worker. // AIPipelineAutoEnabled schedules the daily AI pipeline daily-run on the worker.
// On-demand pipeline routes on web are unaffected. // On-demand pipeline routes on web are unaffected.
AIPipelineAutoEnabled bool `env:"WORKER_AI_PIPELINE_AUTO_ENABLED" envDefault:"true"` AIPipelineAutoEnabled bool `env:"WORKER_AI_PIPELINE_AUTO_ENABLED" envDefault:"true"`
// AIPipelineAutoInterval runs once per day by default at 11:00 UTC (after TED scrape at 10:00). // AIPipelineAutoInterval runs daily-run once per day by default at 11:00 UTC (after TED scrape at 10:00).
AIPipelineAutoInterval string `env:"WORKER_AI_PIPELINE_AUTO_INTERVAL" envDefault:"0 0 11 * * *"` AIPipelineAutoInterval string `env:"WORKER_AI_PIPELINE_AUTO_INTERVAL" envDefault:"0 0 11 * * *"`
} }
-56
View File
@@ -1,56 +0,0 @@
package workers
import (
"context"
"errors"
"time"
"tm/pkg/ai_summarizer"
"tm/pkg/logger"
)
// AIPipelineAutoWorker triggers the Opplens AI pipeline auto run (sync + complete missing work).
type AIPipelineAutoWorker struct {
Logger logger.Logger
AIClient *ai_summarizer.Client
}
// NewAIPipelineAutoWorker creates a worker that calls POST /pipeline/auto on the AI service.
func NewAIPipelineAutoWorker(log logger.Logger, aiClient *ai_summarizer.Client) *AIPipelineAutoWorker {
return &AIPipelineAutoWorker{
Logger: log,
AIClient: aiClient,
}
}
// Run starts the pipeline auto job. A 409 response means a run is already in progress and is not an error.
func (w *AIPipelineAutoWorker) Run() error {
if w.AIClient == nil {
w.Logger.Warn("AI pipeline auto worker skipped: AI client is not configured", map[string]interface{}{})
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
w.Logger.Info("AI pipeline auto worker started", map[string]interface{}{})
resp, err := w.AIClient.PipelineAuto(ctx)
if err != nil {
if errors.Is(err, ai_summarizer.ErrPipelineJobRunning) {
w.Logger.Info("AI pipeline auto skipped: job already running", map[string]interface{}{})
return nil
}
w.Logger.Error("AI pipeline auto worker failed", map[string]interface{}{
"error": err.Error(),
})
return err
}
w.Logger.Info("AI pipeline auto worker triggered successfully", map[string]interface{}{
"status": resp.Status,
"report": resp.Report,
})
return nil
}
+52
View File
@@ -0,0 +1,52 @@
package workers
import (
"context"
"time"
"tm/pkg/ai_summarizer"
"tm/pkg/logger"
)
// AIPipelineDailyWorker triggers the Opplens AI pipeline daily run (sync + full pipeline on newly synced tenders).
type AIPipelineDailyWorker struct {
Logger logger.Logger
AIClient *ai_summarizer.Client
}
// NewAIPipelineDailyWorker creates a worker that calls POST /pipeline/daily-run on the AI service.
func NewAIPipelineDailyWorker(log logger.Logger, aiClient *ai_summarizer.Client) *AIPipelineDailyWorker {
return &AIPipelineDailyWorker{
Logger: log,
AIClient: aiClient,
}
}
// Run starts the pipeline daily-run job. A 409 response means a run is already in progress and is returned as an error
// so the daily job tracker does not mark today as completed before the run actually starts.
func (w *AIPipelineDailyWorker) Run() error {
if w.AIClient == nil {
w.Logger.Warn("AI pipeline daily worker skipped: AI client is not configured", map[string]interface{}{})
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
w.Logger.Info("AI pipeline daily worker started", map[string]interface{}{})
resp, err := w.AIClient.PipelineDailyRun(ctx)
if err != nil {
w.Logger.Error("AI pipeline daily worker failed", map[string]interface{}{
"error": err.Error(),
})
return err
}
w.Logger.Info("AI pipeline daily worker triggered successfully", map[string]interface{}{
"status": resp.Status,
"report": resp.Report,
})
return nil
}
+1 -1
View File
@@ -19,7 +19,7 @@ const (
DailyJobStatusCompleted = "completed" DailyJobStatusCompleted = "completed"
TEDScraperJobName = "ted_scraper" TEDScraperJobName = "ted_scraper"
AIPipelineAutoJobName = "ai_pipeline_auto" AIPipelineDailyJobName = "ai_pipeline_daily"
) )
type dailyJobRun struct { type dailyJobRun struct {