From f68b6d7787ef5f9f23b205fc6d8b828d3d95fe8a Mon Sep 17 00:00:00 2001 From: Mazyar Date: Wed, 8 Jul 2026 02:27:25 +0330 Subject: [PATCH] Refactor recommendation response handling and enhance enrichment logic - Removed the `buildRecommendedTenderResponsesParallel` method and replaced it with `buildRecommendedTenderListResponses`, simplifying the response building process. - Introduced `enrichRecommendedTenderResponsesParallel` to handle enrichment of tender responses in parallel, improving performance. - Updated the `Recommend` method to utilize the new enrichment logic, ensuring timely and efficient processing of recommendations. - Cleaned up unused functions and imports in `recommendation_page_cache.go` and `recommendation_response.go`, enhancing code clarity and maintainability. This update streamlines the recommendation response handling and improves the overall efficiency of the tender recommendation service. --- internal/tender/recommendation_page_cache.go | 73 ++------------------ internal/tender/recommendation_response.go | 69 ++++++++++++++++++ internal/tender/service.go | 21 ++++-- 3 files changed, 91 insertions(+), 72 deletions(-) diff --git a/internal/tender/recommendation_page_cache.go b/internal/tender/recommendation_page_cache.go index 3136aa9..4f51636 100644 --- a/internal/tender/recommendation_page_cache.go +++ b/internal/tender/recommendation_page_cache.go @@ -8,9 +8,6 @@ import ( "time" "github.com/redis/go-redis/v9" - "go.mongodb.org/mongo-driver/v2/bson" - "golang.org/x/sync/errgroup" - "tm/internal/company" "tm/pkg/response" ) @@ -92,7 +89,7 @@ func (s *tenderService) buildRecommendedTendersPageCache(ctx context.Context, co return nil } - responses := s.buildRecommendedTenderResponsesParallel(ctx, items, language) + responses := s.buildRecommendedTenderListResponses(items, language) page := cachedRecommendedTendersPage{ BuiltAt: time.Now().Unix(), CompanyID: companyID, @@ -204,54 +201,6 @@ func (s *tenderService) buildAllRankedRecommendations( return out, nil } -func (s *tenderService) buildRecommendedTenderResponsesParallel( - ctx context.Context, - items []rankedRecommendedTender, - language string, -) []TenderResponse { - if len(items) == 0 { - return nil - } - - out := make([]TenderResponse, len(items)) - if len(items) == 1 { - out[0] = s.buildRecommendedTenderResponse( - ctx, items[0].tender, items[0].rank, items[0].analysis, items[0].tenderRef, language, - newRecommendationTranslationCache(), - ) - return out - } - - translationCache := newRecommendationTranslationCache() - group, groupCtx := errgroup.WithContext(ctx) - group.SetLimit(recommendationPageCacheConcurrency) - - for i := range items { - i := i - item := items[i] - group.Go(func() error { - out[i] = s.buildRecommendedTenderResponse( - groupCtx, item.tender, item.rank, item.analysis, item.tenderRef, language, translationCache, - ) - return nil - }) - } - - if err := group.Wait(); err != nil { - s.logger.Warn("Parallel recommendation page cache build degraded to sequential", map[string]interface{}{ - "error": err.Error(), - "count": len(items), - }) - for i, item := range items { - out[i] = s.buildRecommendedTenderResponse( - ctx, item.tender, item.rank, item.analysis, item.tenderRef, language, translationCache, - ) - } - } - - return out -} - func (s *tenderService) recommendFromPageCache( ctx context.Context, companyID string, @@ -292,8 +241,11 @@ func (s *tenderService) recommendFromPageCache( end = len(filtered) } + paged := append([]TenderResponse(nil), filtered[start:end]...) + paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang) + return &SearchResponse{ - Tenders: append([]TenderResponse(nil), filtered[start:end]...), + Tenders: paged, Metadata: pagination.ListMeta(total, "", end < len(filtered), start), }, true } @@ -376,21 +328,6 @@ func filterCachedRecommendedTenders( return out } -func tenderFromRecommendationResponse(item TenderResponse) Tender { - var tender Tender - if id, err := bson.ObjectIDFromHex(strings.TrimSpace(item.ID)); err == nil { - tender.ID = id - } - tender.ContractFolderID = item.ContractFolderID - tender.NoticePublicationID = item.NoticePublicationID - tender.RelatedNoticePublicationIDs = append([]string(nil), item.RelatedNoticePublicationIDs...) - tender.Status = item.Status - tender.PublicationDate = item.PublicationDate - tender.SubmissionDeadline = item.SubmissionDeadline - tender.TenderDeadline = item.TenderDeadline - return tender -} - // RecommendedTendersPageCacheKeyPrefix is shared with company invalidation logic. const RecommendedTendersPageCacheKeyPrefix = recommendedTendersPageCacheKeyPrefix diff --git a/internal/tender/recommendation_response.go b/internal/tender/recommendation_response.go index 688ea86..db8fc2f 100644 --- a/internal/tender/recommendation_response.go +++ b/internal/tender/recommendation_response.go @@ -6,6 +6,9 @@ import ( "strings" "sync" + "go.mongodb.org/mongo-driver/v2/bson" + "golang.org/x/sync/errgroup" + "tm/pkg/ai_summarizer" ) @@ -68,6 +71,72 @@ func (s *tenderService) buildRecommendedTenderListResponses(items []rankedRecomm return out } +func (s *tenderService) enrichRecommendedTenderResponsesParallel(ctx context.Context, responses []TenderResponse, language string) []TenderResponse { + if len(responses) == 0 || s.aiSummarizerStorage == nil { + return responses + } + + out := make([]TenderResponse, len(responses)) + if len(responses) == 1 { + out[0] = s.enrichRecommendedTenderResponse(ctx, responses[0], language, newRecommendationTranslationCache()) + return out + } + + translationCache := newRecommendationTranslationCache() + group, groupCtx := errgroup.WithContext(ctx) + group.SetLimit(recommendationPageCacheConcurrency) + + for i := range responses { + i := i + group.Go(func() error { + out[i] = s.enrichRecommendedTenderResponse(groupCtx, responses[i], language, translationCache) + return nil + }) + } + + if err := group.Wait(); err != nil { + for i, resp := range responses { + out[i] = s.enrichRecommendedTenderResponse(ctx, resp, language, translationCache) + } + } + + return out +} + +func (s *tenderService) enrichRecommendedTenderResponse( + ctx context.Context, + resp TenderResponse, + language string, + cache *recommendationTranslationCache, +) TenderResponse { + enriched := resp + tender := tenderFromRecommendationResponse(resp) + s.enrichWithTranslationTimeout( + ctx, + &tender, + &enriched, + language, + cache, + recommendTranslationEnrichmentTimeout, + ) + return enriched +} + +func tenderFromRecommendationResponse(item TenderResponse) Tender { + var tender Tender + if id, err := bson.ObjectIDFromHex(strings.TrimSpace(item.ID)); err == nil { + tender.ID = id + } + tender.ContractFolderID = item.ContractFolderID + tender.NoticePublicationID = item.NoticePublicationID + tender.RelatedNoticePublicationIDs = append([]string(nil), item.RelatedNoticePublicationIDs...) + tender.Status = item.Status + tender.PublicationDate = item.PublicationDate + tender.SubmissionDeadline = item.SubmissionDeadline + tender.TenderDeadline = item.TenderDeadline + return tender +} + func (s *tenderService) loadRecommendationExclusions( ctx context.Context, form *SearchForm, diff --git a/internal/tender/service.go b/internal/tender/service.go index 205d2b0..c02b081 100644 --- a/internal/tender/service.go +++ b/internal/tender/service.go @@ -96,8 +96,9 @@ type tenderService struct { } const ( - aiSummaryEnrichmentTimeout = 2 * time.Second - aiTranslationEnrichmentTimeout = 2 * time.Second + aiSummaryEnrichmentTimeout = 2 * time.Second + aiTranslationEnrichmentTimeout = 2 * time.Second + recommendTranslationEnrichmentTimeout = 5 * time.Second ) var ( @@ -214,6 +215,17 @@ func (s *tenderService) enrichWithAISummary(ctx context.Context, t *Tender, resp // enrichWithTranslation overlays title/description from MinIO when the pipeline has finished // for the requested language. Does not call the AI translate API on GET. func (s *tenderService) enrichWithTranslation(ctx context.Context, t *Tender, resp *TenderResponse, language string, cache *recommendationTranslationCache) { + s.enrichWithTranslationTimeout(ctx, t, resp, language, cache, aiTranslationEnrichmentTimeout) +} + +func (s *tenderService) enrichWithTranslationTimeout( + ctx context.Context, + t *Tender, + resp *TenderResponse, + language string, + cache *recommendationTranslationCache, + enrichmentTimeout time.Duration, +) { if t == nil || resp == nil { return } @@ -233,7 +245,7 @@ func (s *tenderService) enrichWithTranslation(ctx context.Context, t *Tender, re } } - enrichCtx, cancel := context.WithTimeout(ctx, aiTranslationEnrichmentTimeout) + enrichCtx, cancel := context.WithTimeout(ctx, enrichmentTimeout) defer cancel() stored, usedNotice, err := s.lookupTranslationForTender(enrichCtx, t, targetLanguage) @@ -1027,7 +1039,8 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat return nil, err } - paged := s.buildRecommendedTenderResponsesParallel(ctx, pageResult.items, lang) + paged := s.buildRecommendedTenderListResponses(pageResult.items, lang) + paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang) return &SearchResponse{ Tenders: paged,