package workers import ( "context" "strings" "time" "tm/internal/tender" "tm/pkg/ai_summarizer" "tm/pkg/logger" "tm/pkg/mongo" ) // TranslationWorker backfills missing tender translations using AI summarizer API. type TranslationWorker struct { Mongo *mongo.ConnectionManager Logger logger.Logger TenderRepo tender.TenderRepository AIClient *ai_summarizer.Client TargetLanguage string BatchSize int } // NewTranslationWorker creates a translation worker for a target language. func NewTranslationWorker(mongo *mongo.ConnectionManager, logger logger.Logger, tenderRepo tender.TenderRepository, aiClient *ai_summarizer.Client, targetLanguage string, batchSize int) *TranslationWorker { if strings.TrimSpace(targetLanguage) == "" { targetLanguage = "en" } if batchSize <= 0 { batchSize = 20 } return &TranslationWorker{ Mongo: mongo, Logger: logger, TenderRepo: tenderRepo, AIClient: aiClient, TargetLanguage: strings.ToLower(strings.TrimSpace(targetLanguage)), BatchSize: batchSize, } } // Run starts backfilling translations for tenders missing target-language content. func (w *TranslationWorker) Run() { if w.AIClient == nil { w.Logger.Warn("Translation worker skipped: AI client is not configured", map[string]interface{}{ "target_language": w.TargetLanguage, }) return } w.Logger.Info("Translation worker started", map[string]interface{}{ "target_language": w.TargetLanguage, "batch_size": w.BatchSize, }) for { // Always use skip=0 because successfully translated tenders are excluded by filter. tenders, totalCount, err := w.TenderRepo.GetUnTranslatedTenders(context.Background(), w.TargetLanguage, w.BatchSize, 0) if err != nil { w.Logger.Error("Failed to fetch untranslated tenders", map[string]interface{}{ "target_language": w.TargetLanguage, "error": err.Error(), }) break } w.Logger.Info("Fetched untranslated tenders batch", map[string]interface{}{ "target_language": w.TargetLanguage, "batch_count": len(tenders), "total_count": totalCount, }) if len(tenders) == 0 { break } for _, t := range tenders { w.translateTender(&t) } } w.Logger.Info("Translation worker completed", map[string]interface{}{ "target_language": w.TargetLanguage, }) } func (w *TranslationWorker) translateTender(t *tender.Tender) { if t.NoticePublicationID == "" { return } if strings.TrimSpace(t.ContractFolderID) == "" { w.Logger.Debug("Skipping tender translation: missing contract_folder_id", map[string]interface{}{ "tender_id": t.ID.Hex(), }) return } if existing, ok := t.Translations[w.TargetLanguage]; ok && strings.TrimSpace(existing.Title) != "" { w.Logger.Debug("Skipping tender translation because target language already exists", map[string]interface{}{ "tender_id": t.ID.Hex(), "notice_id": t.NoticePublicationID, "target_language": w.TargetLanguage, }) return } resp, err := w.AIClient.FetchTranslationOnDemand(context.Background(), ai_summarizer.TranslateRequest{ NoticeID: t.NoticePublicationID, ContractFolderID: t.ContractFolderID, NoticePublicationID: t.NoticePublicationID, Title: t.Title, Description: t.Description, Language: w.TargetLanguage, }) if err != nil { w.Logger.Error("Failed to translate tender", map[string]interface{}{ "tender_id": t.ID.Hex(), "notice_id": t.NoticePublicationID, "target_language": w.TargetLanguage, "translation_error": err.Error(), }) return } if t.Translations == nil { t.Translations = make(map[string]tender.TranslationEntry) } now := time.Now().Unix() // Only update the target-language slot. Existing translations in other // languages (e.g., "de", "fr") are preserved and never modified. t.Translations[w.TargetLanguage] = tender.TranslationEntry{ Title: resp.TranslatedTitle, Description: resp.TranslatedDescription, Language: w.TargetLanguage, TranslatedAt: now, } if t.ProcessingMetadata.TranslatedData == nil { t.ProcessingMetadata.TranslatedData = make(map[string]string) } t.ProcessingMetadata.TranslatedData[w.TargetLanguage] = "done" t.ProcessingMetadata.TranslatedAt = now if err := w.TenderRepo.Update(context.Background(), t); err != nil { w.Logger.Error("Failed to persist translated tender", map[string]interface{}{ "tender_id": t.ID.Hex(), "notice_id": t.NoticePublicationID, "target_language": w.TargetLanguage, "persistence_error": err.Error(), }) return } w.Logger.Info("Tender translated successfully", map[string]interface{}{ "tender_id": t.ID.Hex(), "notice_id": t.NoticePublicationID, "target_language": w.TargetLanguage, }) }