Files
tm_back/cmd/worker/workers/summarizer.go
T
Mazyar dcf19b91cd Implement AI pipeline operations in the admin API
- Added new routes and handlers for AI pipeline operations, including scraping documents, batch summarization, translation, and syncing with the Opplens AI service.
- Introduced request forms for handling tender references and batch operations.
- Enhanced the AI service with methods for triggering batch operations and managing pipeline runs.
- Updated Swagger documentation to reflect the new AI pipeline endpoints and their functionalities.

This update integrates comprehensive AI pipeline capabilities into the tender management system, improving operational efficiency and user experience.
2026-06-14 12:54:13 +03:30

209 lines
5.9 KiB
Go

package workers
import (
"context"
"strings"
"time"
"tm/internal/tender"
ai_summarizer "tm/pkg/ai_summarizer"
"tm/pkg/logger"
"tm/pkg/mongo"
)
// DocumentSummarizationWorker triggers AI summarization for scraped tenders.
// The AI service owns MinIO artefacts; this worker marks Mongo processing flags
// after the pipeline or on-demand summarize API completes.
type DocumentSummarizationWorker struct {
Mongo *mongo.ConnectionManager
Logger logger.Logger
TenderRepo tender.TenderRepository
AIClient *ai_summarizer.Client
AIStorage *ai_summarizer.StorageClient
}
// NewDocumentSummarizationWorker creates a document summarization worker.
func NewDocumentSummarizationWorker(
mongo *mongo.ConnectionManager,
logger logger.Logger,
tenderRepo tender.TenderRepository,
aiClient *ai_summarizer.Client,
aiStorage *ai_summarizer.StorageClient,
) *DocumentSummarizationWorker {
return &DocumentSummarizationWorker{
Mongo: mongo,
Logger: logger,
TenderRepo: tenderRepo,
AIClient: aiClient,
AIStorage: aiStorage,
}
}
// Run starts the document summarization process.
func (w *DocumentSummarizationWorker) Run() {
w.Logger.Info("Document summarization worker started", map[string]interface{}{})
if w.AIClient == nil {
w.Logger.Warn("AI summarizer client not configured, skipping document summarization run", map[string]interface{}{})
return
}
if refs := w.collectUnSummarizedTenderRefs(context.Background()); len(refs) > 0 {
if _, err := w.AIClient.TriggerSummarizeBatch(context.Background(), refs); err != nil {
w.Logger.Warn("Failed to trigger AI summarize batch (continuing with per-tender sync)", map[string]interface{}{
"tender_count": len(refs),
"error": err.Error(),
})
}
}
limit := 5
skip := 0
for {
tenders, totalCount, err := w.TenderRepo.GetUnSummarizedTenders(context.Background(), limit, skip)
if err != nil {
w.Logger.Error("Failed to get un-summarized tenders", map[string]interface{}{
"error": err.Error(),
})
return
}
w.Logger.Info("Found tenders for document summarization", map[string]interface{}{
"count": len(tenders),
"total_count": totalCount,
"skip": skip,
})
if len(tenders) == 0 {
break
}
for i := range tenders {
t := &tenders[i]
w.Logger.Info("Processing tender for document summarization", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
})
w.summarizeTender(t)
}
skip += len(tenders)
if int64(skip) >= totalCount {
break
}
}
w.Logger.Info("Document summarization worker completed", map[string]interface{}{})
}
func (w *DocumentSummarizationWorker) collectUnSummarizedTenderRefs(ctx context.Context) []ai_summarizer.TenderRef {
limit := 100
skip := 0
refs := make([]ai_summarizer.TenderRef, 0)
for {
tenders, totalCount, err := w.TenderRepo.GetUnSummarizedTenders(ctx, limit, skip)
if err != nil {
w.Logger.Warn("Failed to collect tenders for summarize batch", map[string]interface{}{
"error": err.Error(),
})
return refs
}
if len(tenders) == 0 {
return refs
}
for i := range tenders {
t := &tenders[i]
if strings.TrimSpace(t.ContractFolderID) == "" || strings.TrimSpace(t.NoticePublicationID) == "" {
continue
}
refs = append(refs, ai_summarizer.NewTenderRef(t.ContractFolderID, t.NoticePublicationID))
}
skip += len(tenders)
if int64(skip) >= totalCount {
return refs
}
}
}
func (w *DocumentSummarizationWorker) summarizeTender(t *tender.Tender) {
if strings.TrimSpace(t.NoticePublicationID) == "" {
w.Logger.Warn("Skipping tender summarization: missing notice_publication_id", map[string]interface{}{
"tender_id": t.ID.Hex(),
})
w.markTenderSummarized(t)
return
}
if strings.TrimSpace(t.ContractFolderID) == "" {
w.Logger.Warn("Skipping tender summarization: missing contract_folder_id", map[string]interface{}{
"tender_id": t.ID.Hex(),
})
w.markTenderSummarized(t)
return
}
ctx := context.Background()
if w.isSummaryReadyInStorage(ctx, t) {
w.markTenderSummarized(t)
return
}
req := ai_summarizer.NewSummarizeRequest(t.ContractFolderID, t.NoticePublicationID)
if _, err := w.AIClient.FetchSummaryOnDemand(ctx, req); err != nil {
w.Logger.Error("Failed to summarize tender via AI service", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
"contract_folder_id": t.ContractFolderID,
"error": err.Error(),
})
return
}
if !w.isSummaryReadyInStorage(ctx, t) {
w.Logger.Warn("AI summarize completed but storage is not ready yet", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
})
}
w.markTenderSummarized(t)
}
func (w *DocumentSummarizationWorker) isSummaryReadyInStorage(ctx context.Context, t *tender.Tender) bool {
if w.AIStorage == nil {
return false
}
ready, err := w.AIStorage.IsSummaryReady(ctx, t.ContractFolderID, t.NoticePublicationID)
if err != nil {
w.Logger.Debug("Could not check summarization status in storage", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
"error": err.Error(),
})
return false
}
return ready
}
func (w *DocumentSummarizationWorker) markTenderSummarized(t *tender.Tender) {
now := time.Now().Unix()
t.ProcessingMetadata.DocumentsSummarized = true
t.ProcessingMetadata.DocumentsSummarizedAt = now
t.UpdatedAt = now
if err := w.TenderRepo.Update(context.Background(), t); err != nil {
w.Logger.Error("Failed to update tender summarization status", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
"error": err.Error(),
})
return
}
w.Logger.Info("Tender marked as summarized", map[string]interface{}{
"tender_id": t.ID.Hex(),
"notice_publication_id": t.NoticePublicationID,
})
}