Files
tm_back/internal/ai_pipeline/service.go
T
Mazyar e5fa0dfe47
continuous-integration/drone/push Build is passing
Add GetScrapePortals endpoint and related service functionality
- Introduced the GetScrapePortals method in the AI pipeline handler to list document scraping portals supported by the Opplens AI service.
- Updated the service layer to include GetScrapePortals, which retrieves the portals from the client and handles errors appropriately.
- Enhanced the routes to register the new endpoint for retrieving scrape portals.
- Added a new error type for invalid date ranges in the document scraper, improving validation and error handling.

This update expands the AI pipeline capabilities, allowing for better management of document scraping portals within the tender management system.
2026-06-18 19:53:07 +03:30

447 lines
14 KiB
Go

package ai_pipeline
import (
"context"
"fmt"
"tm/pkg/ai_summarizer"
"tm/pkg/logger"
)
// Client defines Opplens AI operations exposed to the admin API.
type Client interface {
ScrapeDocuments(ctx context.Context, reqBody ai_summarizer.ScrapeDocumentsRequest) (*ai_summarizer.ScrapeDocumentsResponse, error)
ScrapeDocumentsBatch(ctx context.Context, tenders []ai_summarizer.TenderRef) (*ai_summarizer.BatchAcceptedResponse, error)
GetScrapePortals(ctx context.Context) (*ai_summarizer.ScrapePortalsResponse, error)
TriggerSummarizeBatch(ctx context.Context, tenders []ai_summarizer.TenderRef) (*ai_summarizer.BatchAcceptedResponse, error)
TriggerTranslateBatch(ctx context.Context, tenders []ai_summarizer.TenderRef, languages []string) (*ai_summarizer.BatchAcceptedResponse, error)
PipelineSync(ctx context.Context) (*ai_summarizer.PipelineSyncResponse, error)
PipelineRun(ctx context.Context, reqBody ai_summarizer.PipelineRunRequest) (map[string]interface{}, error)
PipelineRunBatch(ctx context.Context, tenders []ai_summarizer.TenderRef, languages []string) (*ai_summarizer.BatchAcceptedResponse, error)
PipelineAnalyze(ctx context.Context) (*ai_summarizer.PipelineActionResponse, error)
PipelineDailyRun(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error)
PipelineAuto(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error)
GetPipelineLastRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error)
GetPipelineLastAutoRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error)
GetPipelineReport(ctx context.Context, window string) (*ai_summarizer.PipelineReportResponse, error)
GetPipelineStats(ctx context.Context, window string) (*ai_summarizer.PipelineStatsResponse, error)
GetPipelineMinioStats(ctx context.Context) (ai_summarizer.PipelineMinioStatsResponse, error)
}
// Service proxies admin pipeline operations to the Opplens AI service.
type Service interface {
ScrapeDocuments(ctx context.Context, form ScrapeDocumentsForm) (*ai_summarizer.ScrapeDocumentsResponse, error)
ScrapeDocumentsBatch(ctx context.Context, form TenderBatchForm) (*ai_summarizer.BatchAcceptedResponse, error)
GetScrapePortals(ctx context.Context) (*ai_summarizer.ScrapePortalsResponse, error)
SummarizeBatch(ctx context.Context, form TenderBatchForm) (*ai_summarizer.BatchAcceptedResponse, error)
TranslateBatch(ctx context.Context, form TranslateBatchForm) (*ai_summarizer.BatchAcceptedResponse, error)
Sync(ctx context.Context) (*ai_summarizer.PipelineSyncResponse, error)
Run(ctx context.Context, form PipelineRunForm) (map[string]interface{}, error)
RunBatch(ctx context.Context, form PipelineRunBatchForm) (*ai_summarizer.BatchAcceptedResponse, error)
Analyze(ctx context.Context) (*ai_summarizer.PipelineActionResponse, error)
DailyRun(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error)
Auto(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error)
GetLastRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error)
GetLastAutoRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error)
GetReport(ctx context.Context, window string) (*ai_summarizer.PipelineReportResponse, error)
GetStats(ctx context.Context, window string) (*ai_summarizer.PipelineStatsResponse, error)
GetMinioStats(ctx context.Context) (ai_summarizer.PipelineMinioStatsResponse, error)
}
// ScrapedDocumentMetadataSyncer persists MinIO scraped-document metadata onto tender records.
type ScrapedDocumentMetadataSyncer interface {
SyncScrapedDocumentsFromStorage(ctx context.Context, contractFolderID, noticePublicationID string) error
}
type service struct {
client Client
logger logger.Logger
metadataSyncer ScrapedDocumentMetadataSyncer
}
// NewService creates an AI pipeline admin service.
func NewService(client Client, log logger.Logger, metadataSyncer ScrapedDocumentMetadataSyncer) Service {
return &service{
client: client,
logger: log,
metadataSyncer: metadataSyncer,
}
}
func (s *service) requireClient() error {
if s.client == nil {
return ErrAINotConfigured
}
return nil
}
func (s *service) ScrapeDocuments(ctx context.Context, form ScrapeDocumentsForm) (*ai_summarizer.ScrapeDocumentsResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin scrape documents requested", map[string]interface{}{
"contract_folder_id": form.ContractFolderID,
"notice_publication_id": form.NoticePublicationID,
})
resp, err := s.client.ScrapeDocuments(ctx, ai_summarizer.ScrapeDocumentsRequest{
ContractFolderID: form.ContractFolderID,
NoticePublicationID: form.NoticePublicationID,
})
if err != nil {
s.logger.Error("Admin scrape documents failed", map[string]interface{}{
"contract_folder_id": form.ContractFolderID,
"notice_publication_id": form.NoticePublicationID,
"error": err.Error(),
})
return nil, fmt.Errorf("scrape documents failed: %w", err)
}
if resp != nil && resp.Success && resp.FilesDownloaded > 0 {
s.syncScrapedDocumentMetadata(ctx, form.ContractFolderID, form.NoticePublicationID)
}
return resp, nil
}
func (s *service) ScrapeDocumentsBatch(ctx context.Context, form TenderBatchForm) (*ai_summarizer.BatchAcceptedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
refs, err := validateTenderBatch(form.Tenders)
if err != nil {
return nil, err
}
s.logger.Info("Admin scrape documents batch requested", map[string]interface{}{
"tender_count": len(refs),
})
resp, err := s.client.ScrapeDocumentsBatch(ctx, refs)
if err != nil {
s.logger.Error("Admin scrape documents batch failed", map[string]interface{}{
"tender_count": len(refs),
"error": err.Error(),
})
return nil, fmt.Errorf("scrape documents batch failed: %w", err)
}
return resp, nil
}
func (s *service) GetScrapePortals(ctx context.Context) (*ai_summarizer.ScrapePortalsResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin get scrape portals requested", map[string]interface{}{})
resp, err := s.client.GetScrapePortals(ctx)
if err != nil {
s.logger.Error("Admin get scrape portals failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("get scrape portals failed: %w", err)
}
return resp, nil
}
func (s *service) SummarizeBatch(ctx context.Context, form TenderBatchForm) (*ai_summarizer.BatchAcceptedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
refs, err := validateTenderBatch(form.Tenders)
if err != nil {
return nil, err
}
s.logger.Info("Admin summarize batch requested", map[string]interface{}{
"tender_count": len(refs),
})
resp, err := s.client.TriggerSummarizeBatch(ctx, refs)
if err != nil {
s.logger.Error("Admin summarize batch failed", map[string]interface{}{
"tender_count": len(refs),
"error": err.Error(),
})
return nil, fmt.Errorf("summarize batch failed: %w", err)
}
return resp, nil
}
func (s *service) TranslateBatch(ctx context.Context, form TranslateBatchForm) (*ai_summarizer.BatchAcceptedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
refs, err := validateTenderBatch(form.Tenders)
if err != nil {
return nil, err
}
languages := normalizeLanguages(form.Languages)
if len(languages) == 0 {
return nil, ErrEmptyLanguages
}
s.logger.Info("Admin translate batch requested", map[string]interface{}{
"tender_count": len(refs),
"languages": languages,
})
resp, err := s.client.TriggerTranslateBatch(ctx, refs, languages)
if err != nil {
s.logger.Error("Admin translate batch failed", map[string]interface{}{
"tender_count": len(refs),
"languages": languages,
"error": err.Error(),
})
return nil, fmt.Errorf("translate batch failed: %w", err)
}
return resp, nil
}
func (s *service) Sync(ctx context.Context) (*ai_summarizer.PipelineSyncResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin pipeline sync requested", map[string]interface{}{})
resp, err := s.client.PipelineSync(ctx)
if err != nil {
s.logger.Error("Admin pipeline sync failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline sync failed: %w", err)
}
return resp, nil
}
func (s *service) Run(ctx context.Context, form PipelineRunForm) (map[string]interface{}, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
languages := normalizeLanguages(form.Languages)
if len(languages) == 0 {
return nil, ErrEmptyLanguages
}
req := ai_summarizer.PipelineRunRequest{
ContractFolderID: form.ContractFolderID,
NoticePublicationID: form.NoticePublicationID,
Languages: languages,
}
s.logger.Info("Admin pipeline run requested", map[string]interface{}{
"contract_folder_id": req.ContractFolderID,
"notice_publication_id": req.NoticePublicationID,
"languages": languages,
})
resp, err := s.client.PipelineRun(ctx, req)
if err != nil {
s.logger.Error("Admin pipeline run failed", map[string]interface{}{
"contract_folder_id": req.ContractFolderID,
"notice_publication_id": req.NoticePublicationID,
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline run failed: %w", err)
}
s.syncScrapedDocumentMetadata(ctx, req.ContractFolderID, req.NoticePublicationID)
return resp, nil
}
func (s *service) RunBatch(ctx context.Context, form PipelineRunBatchForm) (*ai_summarizer.BatchAcceptedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
refs, err := validateTenderBatch(form.Tenders)
if err != nil {
return nil, err
}
languages := normalizeLanguages(form.Languages)
if len(languages) == 0 {
return nil, ErrEmptyLanguages
}
s.logger.Info("Admin pipeline run batch requested", map[string]interface{}{
"tender_count": len(refs),
"languages": languages,
})
resp, err := s.client.PipelineRunBatch(ctx, refs, languages)
if err != nil {
s.logger.Error("Admin pipeline run batch failed", map[string]interface{}{
"tender_count": len(refs),
"languages": languages,
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline run batch failed: %w", err)
}
return resp, nil
}
func (s *service) Analyze(ctx context.Context) (*ai_summarizer.PipelineActionResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin pipeline analyze requested", map[string]interface{}{})
resp, err := s.client.PipelineAnalyze(ctx)
if err != nil {
s.logger.Error("Admin pipeline analyze failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline analyze failed: %w", err)
}
return resp, nil
}
func (s *service) DailyRun(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin pipeline daily-run requested", map[string]interface{}{})
resp, err := s.client.PipelineDailyRun(ctx)
if err != nil {
s.logger.Error("Admin pipeline daily-run failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline daily-run failed: %w", err)
}
return resp, nil
}
func (s *service) Auto(ctx context.Context) (*ai_summarizer.PipelineStartedResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
s.logger.Info("Admin pipeline auto requested", map[string]interface{}{})
resp, err := s.client.PipelineAuto(ctx)
if err != nil {
s.logger.Error("Admin pipeline auto failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("pipeline auto failed: %w", err)
}
return resp, nil
}
func (s *service) GetLastRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
resp, err := s.client.GetPipelineLastRun(ctx)
if err != nil {
s.logger.Error("Admin get pipeline last-run failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("get pipeline last-run failed: %w", err)
}
return resp, nil
}
func (s *service) GetLastAutoRun(ctx context.Context) (*ai_summarizer.PipelineReportResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
resp, err := s.client.GetPipelineLastAutoRun(ctx)
if err != nil {
s.logger.Error("Admin get pipeline last-auto-run failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("get pipeline last-auto-run failed: %w", err)
}
return resp, nil
}
func (s *service) GetReport(ctx context.Context, window string) (*ai_summarizer.PipelineReportResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
resp, err := s.client.GetPipelineReport(ctx, window)
if err != nil {
s.logger.Error("Admin get pipeline report failed", map[string]interface{}{
"window": window,
"error": err.Error(),
})
return nil, fmt.Errorf("get pipeline report failed: %w", err)
}
return resp, nil
}
func (s *service) GetStats(ctx context.Context, window string) (*ai_summarizer.PipelineStatsResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
resp, err := s.client.GetPipelineStats(ctx, window)
if err != nil {
s.logger.Error("Admin get pipeline stats failed", map[string]interface{}{
"window": window,
"error": err.Error(),
})
return nil, fmt.Errorf("get pipeline stats failed: %w", err)
}
return resp, nil
}
func (s *service) GetMinioStats(ctx context.Context) (ai_summarizer.PipelineMinioStatsResponse, error) {
if err := s.requireClient(); err != nil {
return nil, err
}
resp, err := s.client.GetPipelineMinioStats(ctx)
if err != nil {
s.logger.Error("Admin get pipeline minio stats failed", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("get pipeline minio stats failed: %w", err)
}
return resp, nil
}
func validateTenderBatch(forms []TenderRefForm) ([]ai_summarizer.TenderRef, error) {
if len(forms) == 0 {
return nil, ErrEmptyTenderBatch
}
return toTenderRefs(forms), nil
}
func (s *service) syncScrapedDocumentMetadata(ctx context.Context, contractFolderID, noticePublicationID string) {
if s.metadataSyncer == nil {
return
}
if err := s.metadataSyncer.SyncScrapedDocumentsFromStorage(ctx, contractFolderID, noticePublicationID); err != nil {
s.logger.Warn("Failed to sync scraped document metadata to Mongo", map[string]interface{}{
"contract_folder_id": contractFolderID,
"notice_publication_id": noticePublicationID,
"error": err.Error(),
})
}
}