dcf19b91cd
- 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.
209 lines
5.9 KiB
Go
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,
|
|
})
|
|
}
|