Files
tm_back/cmd/worker/workers/translation.go
T
2026-05-13 14:50:37 +03:30

158 lines
4.7 KiB
Go

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,
})
}